├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── webruby ├── driver └── driver.c ├── lib ├── webruby.rb └── webruby │ ├── app.rb │ ├── config.rb │ ├── environment.rb │ ├── rake │ ├── files.rake │ ├── general.rake │ └── mruby.rake │ └── utility.rb ├── scripts ├── gen_gems_config.rb ├── gen_post.rb └── gen_require.rb ├── templates └── minimal │ ├── Rakefile │ └── app │ └── app.rb └── webruby.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "modules/mruby"] 2 | path = modules/mruby 3 | url = git://github.com/mruby/mruby.git 4 | [submodule "modules/mrubymix"] 5 | path = modules/mrubymix 6 | url = git://github.com/xxuejie/mrubymix.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - sudo add-apt-repository -y ppa:kalakris/cmake 3 | - sudo apt-get update -qq 4 | - sudo apt-get install cmake 5 | - wget https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz 6 | - tar xzf emsdk-portable.tar.gz 7 | - cd emsdk_portable 8 | - ./emsdk list 9 | - ./emsdk install sdk-1.30.0-64bit 10 | - ./emsdk activate sdk-1.30.0-64bit 11 | - cd .. 12 | - gem build webruby.gemspec 13 | - mv `ls webruby-*.gem` webruby-latest.gem 14 | - gem install --no-ri --no-rdoc ./webruby-latest.gem 15 | - webruby new SampleProject 16 | # Just copy-paste version of fix from https://github.com/travis-ci/travis-cookbooks/issues/155 17 | - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm 18 | script: "cd SampleProject && rake && rake && rake mrbtest" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 - 2015 Xuejie Xiao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This project brings mruby to the browser. It uses [emscripten] 4 | (https://github.com/kripken/emscripten) to compile the mruby source code into 5 | JavaScript and runs in the browser. 6 | 7 | # Build Status 8 | 9 | Since emscripten SDK [does not provide](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html#linux) pre-built binaries for Linux, we cannot use Travis CI right now. We want to be nice and don't try to rebuild whole LLVM each time we are pushing new code :) 10 | 11 | # How to Install 12 | 13 | Webruby now depends on [emsdk](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html) to provide emscripten and LLVM infrustructure. To install webruby, following the following steps: 14 | 15 | 1. Install emsdk following instructions at [here](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html) 16 | 2. Install latest incoming version of emscripten sdk(right now webruby still depends on code from incoming branch of emscripten, once this goes into a release version, we will lock the version for better stability) 17 | 3. Activate latest incoming version 18 | 4. Webruby should be able to pick up the correct version of emscripten from emsdk. If not, feel free to create an issue :) 19 | 20 | # Notes 21 | 22 | Thanks to @scalone and @sadasant, webruby is already used in production: http://rubykaigi.org/2014/presentation/S-ThiagoScalone-DanielRodriguez 23 | 24 | However, you might still want to give it a full test before using it in production :) 25 | 26 | # Demos 27 | 28 | * [webruby irb](http://joshnuss.github.io/mruby-web-irb/) - A nice-looking full-fledged webruby irb. Thanks to @joshnuss for his work! 29 | * [Webruby tutorial](http://qiezi.me/projects/webruby-tutorial/) - minimal example of webruby project, a full description is at [here](http://blog.qiezi.me/posts/84789-webruby-1-2-3-tutorial) 30 | * [mruby](http://qiezi.me/projects/mruby-web-irb/mruby.html) - This is only a minimal demo of web irb, if you want to try out mruby in a browser, I strongly suggest the demo above. 31 | * [geometries](http://qiezi.me/projects/webgl/geometries.html) - a WebGL example using webruby, [mruby-js](https://github.com/xxuejie/mruby-js) and [three.js](https://github.com/mrdoob/three.js/). **NOTE**: from a practical point of view, I agree that this can be easily implemented using JS. However, this demo shows how easy it is to interact with the JavaScript environment using Ruby. 32 | 33 | # License 34 | 35 | This project is distributed under the MIT License. See LICENSE for further details. 36 | -------------------------------------------------------------------------------- /bin/webruby: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # %% -*- ruby -*- 3 | 4 | require 'fileutils' 5 | 6 | def show_help_and_exit! 7 | puts < 6 | #include 7 | 8 | #include "mruby.h" 9 | #include "mruby/compile.h" 10 | #include "mruby/irep.h" 11 | 12 | /* The generated mruby bytecodes are stored in this array */ 13 | extern const uint8_t app_irep[]; 14 | 15 | #ifdef HAS_REQUIRE 16 | void mrb_enable_require(mrb_state *mrb); 17 | #endif 18 | 19 | /* 20 | * Print levels: 21 | * 0 - Do not print anything 22 | * 1 - Print errors only 23 | * 2 - Print errors and results 24 | */ 25 | static int check_and_print_errors(mrb_state* mrb, mrb_value result, 26 | int print_level) 27 | { 28 | if (mrb->exc && (print_level > 0)) { 29 | mrb_p(mrb, mrb_obj_value(mrb->exc)); 30 | mrb->exc = 0; 31 | return 1; 32 | } 33 | 34 | if (print_level > 1) { 35 | mrb_p(mrb, result); 36 | } 37 | return 0; 38 | } 39 | 40 | int webruby_internal_run_bytecode(mrb_state* mrb, const uint8_t *bc, 41 | int print_level) 42 | { 43 | return check_and_print_errors(mrb, mrb_load_irep(mrb, bc), print_level); 44 | } 45 | 46 | int webruby_internal_run(mrb_state* mrb, int print_level) 47 | { 48 | return webruby_internal_run_bytecode(mrb, app_irep, print_level); 49 | } 50 | 51 | int webruby_internal_run_source(mrb_state* mrb, const char *s, int print_level) 52 | { 53 | mrbc_context *c = NULL; 54 | int err; 55 | 56 | if (print_level > 0) { 57 | c = mrbc_context_new(mrb); 58 | c->dump_result = TRUE; 59 | } 60 | err = check_and_print_errors(mrb, mrb_load_string_cxt(mrb, s, c), 61 | print_level); 62 | if (c) { 63 | mrbc_context_free(mrb, c); 64 | } 65 | return err; 66 | } 67 | 68 | int webruby_internal_setup(mrb_state* mrb) 69 | { 70 | #ifdef HAS_REQUIRE 71 | mrb_enable_require(mrb); 72 | #endif 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /lib/webruby.rb: -------------------------------------------------------------------------------- 1 | CURRENT_DIR = File.dirname(__FILE__) 2 | 3 | require 'webruby/app' 4 | require 'webruby/config' 5 | require 'webruby/environment' 6 | require 'webruby/utility' 7 | -------------------------------------------------------------------------------- /lib/webruby/app.rb: -------------------------------------------------------------------------------- 1 | module Webruby 2 | class App 3 | class << self 4 | def config 5 | @config ||= Webruby::Config.new 6 | end 7 | 8 | def setup(&block) 9 | block.call(config) 10 | 11 | # load rake tasks 12 | require 'rake' 13 | Dir.glob("#{CURRENT_DIR}/webruby/rake/*.rake") { |f| load f; } 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/webruby/config.rb: -------------------------------------------------------------------------------- 1 | module Webruby 2 | class Config 3 | attr_accessor :entrypoint, :build_dir, :selected_gemboxes, :selected_gems, 4 | :compile_mode, :loading_mode, :output_name, 5 | :append_file, :source_processor, :cflags, :ldflags, :static_libs 6 | 7 | def initialize 8 | @entrypoint = 'app/app.rb' 9 | @build_dir = 'build' 10 | @selected_gemboxes = ['default'] 11 | @selected_gems = [] 12 | @compile_mode = 'debug' # debug or release 13 | @loading_mode = 2 14 | @output_name = 'webruby.js' 15 | @source_processor = :mrubymix 16 | @cflags = %w(-Wall -Werror-implicit-function-declaration -Wno-warn-absolute-paths) + [optimization_flag] 17 | @ldflags = [] 18 | @static_libs = [] 19 | end 20 | 21 | def optimization_flag 22 | return compile_mode if compile_mode.start_with? "-" 23 | compile_mode == "release" ? "-O2" : "-O0" 24 | end 25 | 26 | def gem(g) 27 | selected_gems << g 28 | end 29 | 30 | def gembox(gb) 31 | selected_gemboxes << gb 32 | end 33 | 34 | def gembox_lines 35 | generate_conf_lines(selected_gemboxes, 'gembox') 36 | end 37 | 38 | def gem_lines 39 | generate_conf_lines(selected_gems, 'gem') 40 | end 41 | 42 | private 43 | def generate_conf_lines(arr, option) 44 | arr.map { |i| "conf.#{option}(#{format_gem(i)})" 45 | }.inject { |a, b| "#{a}\n #{b}" } 46 | end 47 | 48 | def format_gem(gem) 49 | return gem if gem.is_a?(Hash) 50 | "'#{gem}'" 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/webruby/environment.rb: -------------------------------------------------------------------------------- 1 | BASE_DIR = File.expand_path(File.join(File.dirname(__FILE__), 2 | %w[.. ..])) 3 | MRUBY_DIR = File.join(BASE_DIR, %w[modules mruby]) 4 | DRIVER_DIR = File.join(BASE_DIR, %w[driver]) 5 | SCRIPTS_DIR = File.join(BASE_DIR, %w[scripts]) 6 | 7 | # for compatibility with mruby 8 | def root 9 | MRUBY_DIR 10 | end 11 | 12 | emscripten_dir = ENV["EMSCRIPTEN"] 13 | unless emscripten_dir 14 | # Read ~/.emscripten if needed 15 | file = File.join(ENV["HOME"], ".emscripten") 16 | if File.exists?(file) 17 | File.readlines(file).each do |line| 18 | m = line.match(/EMSCRIPTEN_ROOT='([^']+)'/) 19 | if m 20 | emscripten_dir = m[1] 21 | end 22 | end 23 | ENV["EMSCRIPTEN"] = emscripten_dir 24 | end 25 | end 26 | 27 | unless emscripten_dir && emscripten_dir.length > 0 28 | puts <<__EOF__ 29 | WARNING: We found out that you have not configured emscripten. Please 30 | install emsdk followings steps at http://kripken.github.io/emscripten-site/ 31 | and rerun this command later. 32 | __EOF__ 33 | exit(1) 34 | end 35 | 36 | EMCC = File.join(emscripten_dir, 'emcc') 37 | EMXX = File.join(emscripten_dir, 'em++') 38 | EMLD = File.join(emscripten_dir, 'emcc') 39 | EMAR = File.join(emscripten_dir, 'emar') 40 | 41 | # TODO: maybe change these two to functions? 42 | SCRIPT_GEN_POST = File.join(SCRIPTS_DIR, "gen_post.rb") 43 | SCRIPT_GEN_GEMS_CONFIG = File.join(SCRIPTS_DIR, "gen_gems_config.rb") 44 | SCRIPT_GEN_REQUIRE = File.join(SCRIPTS_DIR, "gen_require.rb") 45 | 46 | EMCC_CFLAGS = "-I#{MRUBY_DIR}/include" 47 | 48 | LIBMRUBY = "mruby/emscripten/lib/libmruby.a" 49 | MRBTEST = "mruby/emscripten/test/mrbtest" 50 | MRBC = "mruby/host/bin/mrbc" 51 | 52 | # TODO: change this to a gem dependency 53 | MRUBYMIX = File.join(BASE_DIR, %w[modules mrubymix bin mrubymix]) 54 | 55 | unless File.exists?(File.join(Dir.home, ".emscripten")) 56 | puts <<__EOF__ 57 | WARNING: We found out that you have never run emscripten before, since 58 | emscripten needs a little configuration, we will run emcc here once and 59 | exit. Please follow the instructions given by emcc. When it is finished, 60 | please re-run rake. 61 | __EOF__ 62 | 63 | exec(EMCC) 64 | end 65 | 66 | if `uname -a`.downcase.index("cygwin") 67 | ENV['CYGWIN'] = 'nodosfilewarning' 68 | end 69 | -------------------------------------------------------------------------------- /lib/webruby/rake/files.rake: -------------------------------------------------------------------------------- 1 | file "#{Webruby.build_dir}/gem_library.js" => :gen_gems_config 2 | file "#{Webruby.build_dir}/gem_append.js" => :gen_gems_config 3 | file "#{Webruby.build_dir}/gem_test_library.js" => :gen_gems_config 4 | file "#{Webruby.build_dir}/gem_test_append.js" => :gen_gems_config 5 | 6 | task :gen_gems_config do |t| 7 | sh "ruby #{SCRIPT_GEN_POST} #{Webruby::App.config.loading_mode} #{Webruby.build_dir}/js_api.js" 8 | sh "ruby #{SCRIPT_GEN_GEMS_CONFIG} #{Webruby.build_config} #{Webruby.build_dir}/js_api.js #{Webruby.build_dir}/gem_library.js #{Webruby.build_dir}/gem_append.js #{Webruby.build_dir}/gem_test_library.js #{Webruby.build_dir}/gem_test_append.js #{Webruby.build_dir}/functions #{Webruby.full_build_dir}/mruby/emscripten" 9 | end 10 | 11 | file "#{Webruby.build_dir}/rbcode.c" => [Webruby.entrypoint_file, 12 | :libmruby, 13 | Webruby.build_dir] + 14 | Webruby.rb_files do |t| 15 | if Webruby::App.config.source_processor == :gen_require 16 | ENV["MRBC"] = "#{Webruby.full_build_dir}/#{MRBC}" 17 | sh "ruby #{SCRIPT_GEN_REQUIRE} #{File.expand_path(Webruby.entrypoint_file)} #{Webruby.full_build_dir}/rbcode.c" 18 | else 19 | sh "ruby #{MRUBYMIX} #{Webruby.entrypoint_file} #{Webruby.build_dir}/rbcode.rb" 20 | sh "#{Webruby.build_dir}/#{MRBC} -Bapp_irep -o#{Webruby.build_dir}/rbcode.c #{Webruby.build_dir}/rbcode.rb" 21 | sh "rm #{Webruby.build_dir}/rbcode.rb" 22 | end 23 | end 24 | 25 | file "#{Webruby.build_dir}/app.c" => ["#{Webruby.build_dir}/rbcode.c", 26 | "#{DRIVER_DIR}/driver.c"] do |t| 27 | sh "cat #{DRIVER_DIR}/driver.c #{Webruby.build_dir}/rbcode.c > #{Webruby.build_dir}/app.c" 28 | end 29 | 30 | file "#{Webruby.build_dir}/app.o" => "#{Webruby.build_dir}/app.c" do |t| 31 | require_flag = (Webruby::App.config.source_processor == :gen_require) ? ("-DHAS_REQUIRE") : ("") 32 | sh "#{EMCC} #{EMCC_CFLAGS} #{require_flag} #{Webruby::App.config.cflags.join(' ')} #{Webruby.build_dir}/app.c -o #{Webruby.build_dir}/app.o" 33 | end 34 | 35 | file "#{Webruby.build_dir}/#{Webruby::App.config.output_name}" => 36 | ["#{Webruby.build_dir}/app.o", :libmruby] + Webruby.gem_js_files do |t| 37 | func_arg = Webruby.get_exported_arg("#{Webruby.build_dir}/functions", 38 | Webruby::App.config.loading_mode, 39 | []) 40 | runner_arg = Webruby::App.config.append_file ? "--post-js #{Webruby::App.config.append_file}" : "" 41 | 42 | sh "#{EMLD} #{Webruby.build_dir}/app.o #{Webruby.object_files.join(' ')} #{Webruby::App.config.static_libs.join(' ')} -o #{Webruby.build_dir}/#{Webruby::App.config.output_name} #{Webruby.gem_js_flags} #{func_arg} #{Webruby::App.config.ldflags.join(' ')} #{Webruby::App.config.optimization_flag} #{runner_arg}" 43 | end 44 | 45 | file "#{Webruby.build_dir}/mrbtest.js" => 46 | [:libmruby_test] + Webruby.gem_test_js_files do |t| 47 | # loading mode 0 is necessary for mrbtest 48 | func_arg = Webruby.get_exported_arg("#{Webruby.build_dir}/functions", 49 | 0, ['main']) 50 | 51 | sh "#{EMLD} #{Webruby.test_object_files.join(' ')} #{Webruby.object_files.join(' ')} #{Webruby::App.config.static_libs.join(' ')} -o #{Webruby.build_dir}/mrbtest.js -s TOTAL_MEMORY=33554432 #{Webruby.gem_test_js_flags} #{func_arg} #{Webruby::App.config.ldflags.join(' ')} #{Webruby::App.config.optimization_flag}" 52 | end 53 | -------------------------------------------------------------------------------- /lib/webruby/rake/general.rake: -------------------------------------------------------------------------------- 1 | desc "create build output directory" 2 | directory Webruby.build_dir 3 | 4 | desc "cleanup all generated files" 5 | task :clean do |t| 6 | sh "rm -rf #{Webruby.build_dir}" 7 | end 8 | 9 | task :debug do |t| 10 | puts Webruby::build_dir 11 | puts Webruby::App.config.build_dir 12 | puts MRUBY_DIR 13 | puts EMSCRIPTEN_DIR 14 | end 15 | 16 | task :default => :js 17 | task :js => "#{Webruby.build_dir}/#{Webruby::App.config.output_name}" 18 | 19 | task :mrbtest => "#{Webruby.build_dir}/mrbtest.js" do |t| 20 | sh "cd #{Webruby.build_dir} && node mrbtest.js" 21 | end 22 | -------------------------------------------------------------------------------- /lib/webruby/rake/mruby.rake: -------------------------------------------------------------------------------- 1 | desc "create mruby build configuration" 2 | task Webruby.build_config => Webruby.build_dir do |t| 3 | Webruby::create_file_if_different(Webruby.build_config) do |f| 4 | f.puts <<__EOF__ 5 | # This file is generated by machine, DO NOT EDIT THIS FILE! 6 | MRuby::Build.new do |conf| 7 | toolchain :gcc 8 | conf.build_dir = '#{Webruby.full_build_dir}/mruby/host' 9 | 10 | conf.gembox 'default' 11 | end 12 | 13 | MRuby::Toolchain.new(:emscripten) do |conf| 14 | toolchain :clang 15 | 16 | conf.cc do |cc| 17 | cc.command = '#{EMCC}' 18 | cc.flags.push(*#{Webruby::App.config.cflags.inspect}) 19 | end 20 | 21 | conf.cxx.command = '#{EMLD}' 22 | conf.linker.command = '#{EMLD}' 23 | conf.archiver.command = '#{EMAR}' 24 | end 25 | 26 | MRuby::CrossBuild.new('emscripten') do |conf| 27 | toolchain :emscripten 28 | conf.build_dir = '#{Webruby.full_build_dir}/mruby/emscripten' 29 | conf.gem_clone_dir = '#{File.expand_path("~/.webruby/gems")}' 30 | 31 | #{Webruby::App.config.gembox_lines} 32 | #{Webruby::App.config.gem_lines} 33 | end 34 | __EOF__ 35 | end 36 | end 37 | 38 | desc "build mruby library" 39 | task :libmruby => Webruby.build_config do |t| 40 | ENV["MRUBY_CONFIG"] = Webruby.full_build_config 41 | sh "cd #{MRUBY_DIR} && ruby ./minirake #{Webruby.full_build_dir}/#{LIBMRUBY}" 42 | end 43 | 44 | desc "mruby test library" 45 | task :libmruby_test => Webruby.build_config do |t| 46 | ENV["MRUBY_CONFIG"] = Webruby.full_build_config 47 | sh "cd #{MRUBY_DIR} && ruby ./minirake #{Webruby.full_build_dir}/#{LIBMRUBY} #{Webruby.full_build_dir}/#{MRBTEST}" 48 | end 49 | -------------------------------------------------------------------------------- /lib/webruby/utility.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | module Webruby 4 | class << self 5 | def create_file_if_different(filename) 6 | tmp_filename = "#{filename}.tmp" 7 | 8 | # TODO: add support for case where block is not given, 9 | # maybe using monkey patching on File#close? 10 | f = File.open(tmp_filename, 'w') 11 | yield f 12 | f.close 13 | 14 | if (!File.exists?(filename)) || 15 | (!FileUtils.compare_file(filename, tmp_filename)) 16 | puts "Creating new file: #{filename}!" 17 | FileUtils.cp(tmp_filename, filename) 18 | end 19 | FileUtils.rm(tmp_filename) 20 | end 21 | 22 | def build_dir 23 | Webruby::App.config.build_dir 24 | end 25 | 26 | def full_build_dir 27 | File.expand_path(build_dir) 28 | end 29 | 30 | def build_config 31 | "#{build_dir}/mruby_build_config.rb" 32 | end 33 | 34 | def full_build_config 35 | File.expand_path(build_config) 36 | end 37 | 38 | def entrypoint_file 39 | Webruby::App.config.entrypoint 40 | end 41 | 42 | def object_files 43 | (Dir.glob("#{full_build_dir}/mruby/emscripten/src/**/*.o") + 44 | Dir.glob("#{full_build_dir}/mruby/emscripten/mrblib/**/*.o") + 45 | Dir.glob("#{full_build_dir}/mruby/emscripten/mrbgems/**/*.o")) 46 | .reject { |f| 47 | f.end_with? "gem_test.o" 48 | } 49 | end 50 | 51 | def test_object_files 52 | (Dir.glob("#{full_build_dir}/mruby/emscripten/test/**/*.o") + 53 | Dir.glob("#{full_build_dir}/mruby/emscripten/mrbgems/**/gem_test.o")) 54 | end 55 | 56 | def rb_files 57 | Dir.glob("#{File.dirname(entrypoint_file)}/**") 58 | end 59 | 60 | def gem_js_files 61 | ["#{build_dir}/gem_library.js", "#{build_dir}/gem_append.js"] 62 | end 63 | 64 | def gem_js_flags 65 | "--js-library #{build_dir}/gem_library.js --pre-js #{build_dir}/gem_append.js" 66 | end 67 | 68 | def gem_test_js_files 69 | ["#{build_dir}/gem_test_library.js", "#{build_dir}/gem_test_append.js"] 70 | end 71 | 72 | def gem_test_js_flags 73 | "--js-library #{build_dir}/gem_test_library.js --pre-js #{build_dir}/gem_test_append.js" 74 | end 75 | 76 | # Prepare exported functions for emscripten 77 | 78 | # Webruby now supports 3 kinds of Ruby source code loading methods: 79 | 80 | # * WEBRUBY.run(): this function loads source code compiled from 81 | # the app folder, which is already contained in the js file. 82 | 83 | # * WEBRUBY.run_bytecode(): this function loads an array of mruby 84 | # bytecode, we can generate bytecode using mrbc binary in mruby and 85 | # load the source code at runtime. 86 | 87 | # * WEBRUBY.run_source(): this function parses and loads Ruby source 88 | # code on the fly. 89 | 90 | # Note that different functions are needed for the 3 different loading methods, 91 | # for example, WEBRUBY.run_source requires all the parsing code is present, 92 | # while the first 2 modes only requires code for loading bytecodes. 93 | # Given these considerations, we allow 3 loading modes in webruby: 94 | 95 | # 0 - only WEBRUBY.run is supported 96 | # 1 - WEBRUBY.run and WEBRUBY.run_bytecode are supported 97 | # 2 - all 3 loading methods are supported 98 | 99 | # It may appear that mode 0 and mode 1 requires the same set of functions 100 | # since they both load bytecodes, but due to the fact that mode 0 only loads 101 | # pre-defined bytecode array, chances are optimizers may perform some tricks 102 | # to eliminate parts of the source code for mode 0. Hence we still distinguish 103 | # mode 0 from mode 1 here 104 | 105 | COMMON_EXPORTED_FUNCTIONS = ['mrb_open', 'mrb_close']; 106 | 107 | # Gets a list of all exported functions including following types: 108 | # * Functions exported by mrbgems 109 | # * Functions required by loading modes 110 | # * Functions that are customly added by users 111 | # 112 | # ==== Attributes 113 | # 114 | # * +gem_function_file+ - File name of functions exported by mrbgems, this is 115 | # generated by scripts/gen_gems_config.rb 116 | # * +loading_mode+ - Loading mode 117 | # * +custom_functions+ - Array of custom functions added by user 118 | def get_exported_functions(gem_function_file, loading_mode, custom_functions) 119 | loading_mode = loading_mode.to_i 120 | 121 | functions = File.readlines(gem_function_file).map {|f| f.strip} 122 | functions = functions.concat(custom_functions) 123 | functions = functions.concat(COMMON_EXPORTED_FUNCTIONS) 124 | 125 | functions << 'webruby_internal_setup' 126 | 127 | # WEBRUBY.run is supported by all loading modes 128 | functions << 'webruby_internal_run' 129 | 130 | # WEBRUBY.run_bytecode 131 | functions << 'webruby_internal_run_bytecode' if loading_mode > 0 132 | 133 | # WEBRUBY.run_source 134 | functions << 'webruby_internal_run_source' if loading_mode > 1 135 | 136 | functions.uniq 137 | end 138 | 139 | # Generate command line option for exported functions, see 140 | # gen_exported_functions for argument details 141 | def get_exported_arg(gem_function_file, loading_mode, custom_functions) 142 | func_str = get_exported_functions(gem_function_file, loading_mode, custom_functions) 143 | .map{|f| "'_#{f}'"}.join ', ' 144 | 145 | "-s EXPORTED_FUNCTIONS=\"[#{func_str}]\"" 146 | end 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /scripts/gen_gems_config.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This script loops through all gems to perform the following three tasks: 4 | # 1. Copy WEBRUBY API file to JS append file(this allows us to add WEBRUBY JS plugins) 5 | # 2. Loop through 'js/lib' directory in each gem for JS libraries to include 6 | # 3. Loop through 'js/append' directory in each gem for JS files to append 7 | # 4. Loop through 'test/js/lib' directory of each gem for test libs to include 8 | # 5. Loop through 'test/js/append' directory in each gem for test files to append 9 | # 6. Collect functions to export for each gem 10 | 11 | require 'fileutils' 12 | 13 | if ARGV.length < 7 14 | puts 'Usage: (path to build config) (path to WEBRUBY API file) (JS lib file name) (JS append file name) (test JS lib file name) (test JS append file name) (output functions file name) (mruby build output path)' 15 | exit 1 16 | end 17 | 18 | MRUBY_DIR = File.join(File.expand_path(File.dirname(__FILE__)), %w[.. modules mruby]) 19 | 20 | CONFIG_FILE = File.expand_path(ARGV[0]) 21 | WEBRUBY_API_FILE = File.expand_path(ARGV[1]) 22 | JS_LIB_FILE = File.expand_path(ARGV[2]) 23 | JS_APPEND_FILE = File.expand_path(ARGV[3]) 24 | TEST_JS_LIB_FILE = File.expand_path(ARGV[4]) 25 | TEST_JS_APPEND_FILE = File.expand_path(ARGV[5]) 26 | EXPORTED_FUNCTIONS_FILE = File.expand_path(ARGV[6]) 27 | MRUBY_BUILD_DIR = File.expand_path(ARGV[7]) 28 | 29 | DIRECTORY_MAP = { 30 | 'js/lib' => JS_LIB_FILE, 31 | 'js/append' => JS_APPEND_FILE, 32 | 'test/js/lib' => TEST_JS_LIB_FILE, 33 | 'test/js/append' => TEST_JS_APPEND_FILE 34 | } 35 | 36 | def get_temp_file_name(filename) 37 | "#{filename}.tmp" 38 | end 39 | 40 | def write_file_with_name(fd, file_to_write) 41 | fd.write("/* #{file_to_write} */\n") 42 | fd.write(File.read(file_to_write)) 43 | end 44 | 45 | # monkey patch for getting mrbgem list 46 | $gems = [] 47 | module MRuby 48 | # Toolchain and Build classes are not used, we just 49 | # add them here to prevent runtime error. 50 | class Toolchain 51 | def initialize(sym, &block) 52 | end 53 | end 54 | 55 | class Build 56 | def initialize(&block) 57 | end 58 | end 59 | 60 | GemBox = Object.new 61 | class << GemBox 62 | def new(&block); block.call(self); end 63 | def config=(obj); @config = obj; end 64 | def gem(gemdir, &block); @config.gem(gemdir, &block); end 65 | end 66 | 67 | class CrossBuild 68 | attr_accessor :gem_clone_dir 69 | 70 | def initialize(name, &block) 71 | @gem_clone_dir = "#{root}/build/mrbgems" 72 | 73 | if name == "emscripten" 74 | instance_eval(&block) 75 | end 76 | end 77 | 78 | def root 79 | MRUBY_DIR 80 | end 81 | 82 | def toolchain(sym) 83 | # This is also for preventing errors 84 | end 85 | 86 | def build_dir=(dir) 87 | end 88 | 89 | def gembox(gemboxfile) 90 | gembox = File.expand_path("#{gemboxfile}.gembox", "#{root}/mrbgems") 91 | fail "Can't find gembox '#{gembox}'" unless File.exists?(gembox) 92 | GemBox.config = self 93 | instance_eval File.read(gembox) 94 | end 95 | 96 | def gem(gemdir) 97 | gemdir = load_hash_gem(gemdir) if gemdir.is_a?(Hash) 98 | 99 | # Collecting available gemdir 100 | $gems << gemdir if File.exists?(gemdir) 101 | end 102 | 103 | def load_hash_gem(params) 104 | if params[:github] 105 | params[:git] = "https://github.com/#{params[:github]}.git" 106 | elsif params[:bitbucket] 107 | params[:git] = "https://bitbucket.org/#{params[:bitbucket]}.git" 108 | end 109 | 110 | if params[:core] 111 | gemdir = "#{root}/mrbgems/#{params[:core]}" 112 | elsif params[:git] 113 | url = params[:git] 114 | gemdir = "#{gem_clone_dir}/#{url.match(/([-\w]+)(\.[-\w]+|)$/).to_a[1]}" 115 | else 116 | fail "unknown gem option #{params}" 117 | end 118 | 119 | gemdir 120 | end 121 | end 122 | end 123 | 124 | FileUtils.cd(MRUBY_DIR) do 125 | load CONFIG_FILE 126 | 127 | file_map = {} 128 | DIRECTORY_MAP.each do |k, v| 129 | file_map[k] = File.open(get_temp_file_name(v), 'w') 130 | end 131 | functions = [] 132 | 133 | if File.exists? WEBRUBY_API_FILE 134 | write_file_with_name(file_map['js/append'], WEBRUBY_API_FILE) 135 | end 136 | 137 | $gems.each do |gem| 138 | gem = gem.strip 139 | 140 | # gather JavaScript files 141 | file_map.each do |dir, tmp_f| 142 | Dir.glob(File.join(gem, dir, '*.js')) do |f| 143 | write_file_with_name(tmp_f, f) 144 | end 145 | end 146 | 147 | # gather exported functions 148 | functions_file = File.join(gem, 'EXPORTED_FUNCTIONS') 149 | if File.exists?(functions_file) 150 | File.readlines(functions_file).each do |func| 151 | func = func.strip 152 | functions << func if !func.empty? 153 | end 154 | end 155 | end 156 | 157 | # writes js file 158 | file_map.each { |k, f| f.close } 159 | 160 | ['js/append', 'js/lib'].each do |path| 161 | name = get_temp_file_name(DIRECTORY_MAP[path]) 162 | test_name = get_temp_file_name(DIRECTORY_MAP["test/#{path}"]) 163 | tmp_test_name = "#{test_name}.orig" 164 | 165 | FileUtils.mv(test_name, tmp_test_name) 166 | File.open(test_name, 'w') do |f| 167 | f.write(File.read(name)) 168 | f.write(File.read(tmp_test_name)) 169 | end 170 | FileUtils.rm(tmp_test_name) 171 | end 172 | 173 | DIRECTORY_MAP.each do |dir, filename| 174 | tmpname = get_temp_file_name(filename) 175 | if (!File.exists?(filename)) || 176 | (!FileUtils.compare_file(filename, tmpname)) 177 | puts "Creating new file: #{filename}!" 178 | FileUtils.cp(tmpname, filename) 179 | end 180 | FileUtils.rm(tmpname) 181 | end 182 | 183 | # writes functions file 184 | File.open(EXPORTED_FUNCTIONS_FILE, 'w') do |f| 185 | functions.uniq.each do |func| 186 | f.write("#{func}\n") 187 | end 188 | end 189 | end 190 | -------------------------------------------------------------------------------- /scripts/gen_post.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This script generates the post attached JavaScript file according to loading modes 4 | 5 | require 'fileutils' 6 | 7 | def convert_to_valid_loading_mode str 8 | if str.to_i.to_s == str 9 | i = str.to_i 10 | if (i >= 0) && (i <= 2) 11 | return i 12 | end 13 | end 14 | nil 15 | end 16 | 17 | if ARGV.length < 2 18 | puts 'Usage: (loading mode) (output post js file name)' 19 | exit 1 20 | end 21 | 22 | if !(mode = convert_to_valid_loading_mode ARGV[0]) 23 | puts "#{ARGV[0]} is not a valid loading mode!" 24 | exit 1 25 | end 26 | 27 | OUTPUT_JS_FILE = ARGV[1] 28 | OUTPUT_JS_TEMP_FILE = "#{OUTPUT_JS_FILE}.tmp" 29 | 30 | File.open(OUTPUT_JS_TEMP_FILE, 'w') do |f| 31 | f.puts <<__EOF__ 32 | (function() { 33 | function WEBRUBY(opts) { 34 | if (!(this instanceof WEBRUBY)) { 35 | // Well, this is not perfect, but it can at least cover some cases. 36 | return new WEBRUBY(opts); 37 | } 38 | opts = opts || {}; 39 | 40 | // Default print level is errors only 41 | this.print_level = 1; 42 | if (typeof opts.print_level === "number" && opts.print_level >= 0) { 43 | this.print_level = opts.print_level; 44 | } 45 | this.mrb = _mrb_open(); 46 | _webruby_internal_setup(this.mrb); 47 | }; 48 | 49 | WEBRUBY.prototype.close = function() { 50 | _mrb_close(this.mrb); 51 | }; 52 | WEBRUBY.prototype.run = function() { 53 | _webruby_internal_run(this.mrb, this.print_level); 54 | }; 55 | WEBRUBY.prototype.set_print_level = function(level) { 56 | if (level >= 0) this.print_level = level; 57 | }; 58 | __EOF__ 59 | 60 | if mode > 0 61 | # WEBRUBY.run_bytecode 62 | f.puts <<__EOF__ 63 | WEBRUBY.prototype.run_bytecode = function(bc) { 64 | var stack = Runtime.stackSave(); 65 | var addr = Runtime.stackAlloc(bc.length); 66 | var ret; 67 | writeArrayToMemory(bc, addr); 68 | 69 | ret = _webruby_internal_run_bytecode(this.mrb, addr, this.print_level); 70 | 71 | Runtime.stackRestore(stack); 72 | return ret; 73 | }; 74 | __EOF__ 75 | end 76 | 77 | if mode > 1 78 | # WEBRUBY.run_source 79 | f.puts <<__EOF__ 80 | WEBRUBY.prototype.run_source = function(src) { 81 | var stack = Runtime.stackSave(); 82 | var addr = Runtime.stackAlloc(src.length); 83 | var ret; 84 | writeStringToMemory(src, addr); 85 | 86 | ret = _webruby_internal_run_source(this.mrb, addr, this.print_level); 87 | 88 | Runtime.stackRestore(stack); 89 | return ret; 90 | }; 91 | __EOF__ 92 | end 93 | 94 | f.puts <<__EOF__ 95 | 96 | if (typeof window === 'object') { 97 | window['WEBRUBY'] = WEBRUBY; 98 | } else { 99 | global['WEBRUBY'] = WEBRUBY; 100 | } 101 | }) (); 102 | __EOF__ 103 | end 104 | 105 | if (!File.exists?(OUTPUT_JS_FILE)) || 106 | (!FileUtils.compare_file(OUTPUT_JS_FILE, OUTPUT_JS_TEMP_FILE)) 107 | # only copys the file if it does not match the original one 108 | puts 'Creating new post js file!' 109 | FileUtils.cp(OUTPUT_JS_TEMP_FILE, OUTPUT_JS_FILE) 110 | end 111 | FileUtils.rm(OUTPUT_JS_TEMP_FILE) 112 | -------------------------------------------------------------------------------- /scripts/gen_require.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'fileutils' 4 | 5 | def relative_file_name(file_name) 6 | file_name.sub(/\.rb$/, '') 7 | end 8 | 9 | def symbol_name(relative_file_name) 10 | "#{relative_file_name.gsub('/', '_')}_irep" 11 | end 12 | 13 | if ARGV.length < 2 14 | puts "Usage: (entryfile) (outputfile)" 15 | exit 1 16 | end 17 | 18 | ENTRY_FILE = ARGV[0] 19 | OUTPUT_PATH = ARGV[1] 20 | ENTRY_DIRECTORY = File.dirname(ENTRY_FILE) 21 | MRBC = ENV['MRBC'] 22 | 23 | File.open(OUTPUT_PATH, "w") do |f| 24 | f.puts <> #{OUTPUT_PATH}") 37 | end 38 | end 39 | 40 | File.open(OUTPUT_PATH, "a") do |f| 41 | f.puts <exc) { 62 | mrb_p(mrb, mrb_obj_value(mrb->exc)); 63 | mrb->exc = 0; 64 | } 65 | 66 | return mrb_nil_value(); 67 | } 68 | END 69 | end 70 | 71 | RUBY_SOURCE_PATH = "#{OUTPUT_PATH}.tmp" 72 | File.open(RUBY_SOURCE_PATH, "w") do |f| 73 | f.puts < #{index},\n" 81 | end 82 | 83 | f.puts <> #{OUTPUT_PATH}") 125 | FileUtils.rm RUBY_SOURCE_PATH 126 | 127 | File.open(OUTPUT_PATH, "a") do |f| 128 | f.puts <kernel_module; 131 | mrb_define_method(mrb, kernel_module, "require_internal", 132 | mrb_require_internal, ARGS_REQ(1)); 133 | mrb_load_irep(mrb, mrb_require_internal_irep); 134 | } 135 | END 136 | end 137 | -------------------------------------------------------------------------------- /templates/minimal/Rakefile: -------------------------------------------------------------------------------- 1 | require 'webruby' 2 | 3 | # This file sets up the build environment for a webruby project. 4 | Webruby::App.setup do |conf| 5 | # Entrypoint file name 6 | conf.entrypoint = 'app/app.rb' 7 | 8 | # By default, the build output directory is "build/" 9 | conf.build_dir = 'build' 10 | 11 | # Use 'release' for O2 mode build, and everything else for O0 mode. 12 | # Or you can also use '-O0', '-O1', '-O2', '-O3', etc. directly 13 | conf.compile_mode = 'debug' 14 | 15 | # Note that different functions are needed for the 3 different loading methods, 16 | # for example, WEBRUBY.run_source requires all the parsing code is present, 17 | # while the first 2 modes only requires code for loading bytecodes. 18 | # Given these considerations, we allow 3 loading modes in webruby: 19 | # 20 | # 0 - only WEBRUBY.run is supported 21 | # 1 - WEBRUBY.run and WEBRUBY.run_bytecode are supported 22 | # 2 - all 3 loading methods are supported 23 | # 24 | # It may appear that mode 0 and mode 1 requires the same set of functions 25 | # since they both load bytecodes, but due to the fact that mode 0 only loads 26 | # pre-defined bytecode array, chances are optimizers may perform some tricks 27 | # to eliminate parts of the source code for mode 0. Hence we still distinguish 28 | # mode 0 from mode 1 here 29 | conf.loading_mode = 2 30 | 31 | # 2 Ruby source processors are available right now: 32 | # 33 | # :mrubymix - The old one supporting static require 34 | # :gen_require - The new one supporting require 35 | conf.source_processor = :mrubymix 36 | 37 | # By default the final output file name is "webruby.js" 38 | conf.output_name = 'webruby.js' 39 | 40 | # You can append a JS file at the end of the final output file 41 | # For example, a runner file like following can be used to run 42 | # Ruby code automatically: 43 | # 44 | # (function () { 45 | # var w = new WEBRUBY(); 46 | # w.run(); 47 | # }) (); 48 | # 49 | # NOTE: We used to support a js_bin target which will compile 50 | # a `main.c` file to run the code, but now we favor this method 51 | # instead of the old one. 52 | # conf.append_file = 'runner.js' 53 | 54 | # We found that if memory init file is used, browsers will hang 55 | # for a long time without response. As a result, we disable memory 56 | # init file by default. However, you can test this yourself 57 | # and re-enable it by commenting/removing the next line. 58 | conf.ldflags << "--memory-init-file 0" 59 | 60 | # The syntax for adding gems here are kept the same as mruby. 61 | # Below are a few examples: 62 | 63 | # mruby-eval gem, all parsing code will be packed into the final JS! 64 | # conf.gem :core => "mruby-eval" 65 | 66 | # JavaScript calling interface 67 | # conf.gem :git => 'git://github.com/xxuejie/mruby-js.git', :branch => 'master' 68 | 69 | # OpenGL ES 2.0 binding 70 | # conf.gem :git => 'git://github.com/xxuejie/mruby-gles.git', :branch => 'master' 71 | 72 | # Normally we wouldn't use this example gem, I just put it here to show how to 73 | # add a gem on the local file system, you can either use absolute path or relative 74 | # path from mruby root, which is modules/webruby. 75 | # conf.gem "#{root}/examples/mrbgems/c_and_ruby_extension_example" 76 | end 77 | -------------------------------------------------------------------------------- /templates/minimal/app/app.rb: -------------------------------------------------------------------------------- 1 | # This is the entrypoint file for webruby 2 | 3 | 5.times { puts 'Ruby is awesome!' } 4 | -------------------------------------------------------------------------------- /webruby.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'webruby' 3 | s.version = '0.9.3' 4 | s.date = '2015-01-28' 5 | s.summary = 'webruby' 6 | s.description = 'compile your favourite Ruby source code for the browser!' 7 | s.author = 'Xuejie Xiao' 8 | s.email = 'xxuejie@gmail.com' 9 | s.homepage = 'https://github.com/xxuejie/webruby' 10 | s.license = 'MIT' 11 | 12 | s.bindir = 'bin' 13 | s.executables << 'webruby' 14 | 15 | s.files = Dir['lib/**/*'] 16 | s.files += Dir['bin/**/*'] 17 | s.files += Dir['driver/**/*'] 18 | s.files += Dir['scripts/**/*'] 19 | s.files += Dir['templates/**/*'] 20 | s.files += Dir['modules/**/*'] 21 | end 22 | --------------------------------------------------------------------------------