├── .gitignore ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── azure-pipelines.yml ├── bin └── rien ├── lib ├── rien.rb └── rien │ ├── cli.rb │ ├── configurable.rb │ ├── const.rb │ ├── core_ext │ └── string.rb │ ├── decoder.rb │ ├── encoder.rb │ └── version.rb ├── rien.gemspec └── test ├── samples ├── autoload_gem.rb ├── dir │ └── ruby_in_dir.rb ├── load_plain.rb ├── not_ruby.rb ├── plain.rb ├── read_eval_plain.rb ├── require_gem.rb └── require_relative_plain.rb ├── test_helper.rb └── unit ├── cli_helper_test.rb └── encoder_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /test/reports/ 12 | /tmp/ 13 | 14 | # Used by dotenv library to load environment variables. 15 | # .env 16 | 17 | ## Specific to RubyMotion: 18 | .dat* 19 | .repl_history 20 | build/ 21 | *.bridgesupport 22 | build-iPhoneOS/ 23 | build-iPhoneSimulator/ 24 | 25 | ## Specific to RubyMotion (use of CocoaPods): 26 | # 27 | # We recommend against adding the Pods directory to your .gitignore. However 28 | # you should judge for yourself, the pros and cons are mentioned at: 29 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 30 | # 31 | # vendor/Pods/ 32 | 33 | ## Documentation cache and generated files: 34 | /.yardoc/ 35 | /_yardoc/ 36 | /doc/ 37 | /rdoc/ 38 | 39 | ## Environment normalization: 40 | /.bundle/ 41 | /vendor/bundle 42 | /lib/bundler/man/ 43 | 44 | # for a library or gem, you might want to ignore these files since the code is 45 | # intended to run in multiple environments; otherwise, check them in: 46 | # Gemfile.lock 47 | # .ruby-version 48 | # .ruby-gemset 49 | 50 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 51 | .rvmrc 52 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | # Declare your gem's dependencies in flow_core.gemspec. 5 | # Bundler will treat runtime dependencies like base dependencies, and 6 | # development dependencies will be added by default to the :development group. 7 | gemspec 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rien 2 | 3 | Ruby IR Encoding Gem (Experimental) 4 | 5 | [![Build Status](https://dev.azure.com/dsh0416/rien/_apis/build/status/coderemixer.rien?branchName=master)](https://dev.azure.com/dsh0416/rien/_build/latest?definitionId=1&branchName=master) 6 | 7 | # Example 8 | 9 | ``` 10 | Usage: rien [options] 11 | -e, --encode [FILE] Encode specific ruby file 12 | -p, --pack [DIR] Pack ruby directory into encoded files 13 | -o, --out [FILE/DIR] Indicate the output of the encoded file(s) 14 | -u, --use-rienfile Use Rienfile to configure, override other options 15 | -s, --silent-mode Suppress all prompts asking for user input 16 | -t, --tmpdir [DIR] Select a temp directory to store intermediate results 17 | ``` 18 | 19 | ## Encode Mode 20 | 21 | Encode single ruby file 22 | 23 | ```ruby 24 | # hello.rb 25 | 26 | puts "Hello Rien" 27 | ``` 28 | 29 | ``` 30 | rien -e hello.rb -o encoded.rb 31 | 32 | ruby encoded.rb # => Hello Rien 33 | ``` 34 | 35 | ## Pack Mode 36 | 37 | ### Compile all .rb files in the directory 38 | 39 | ``` 40 | rien -p source -o output 41 | ``` 42 | 43 | ### Use Rienfile to configure 44 | 45 | When using `Rienfile` to configure, other options from CLI will be ignored 46 | 47 | Put a `Rienfile` in your project directory 48 | 49 | ```ruby 50 | # source/Rienfile 51 | 52 | Rien.configure do |config| 53 | # The syntax is the same as glob used in class Dir 54 | # [Optional] includes all files by default 55 | config.includes = ["app/**/*", "lib/**/*"] 56 | # [Optional] excludes nothing by default 57 | config.excludes = ["**/*.py", 58 | "lib/**/templates/**/*", 59 | "**/init.rb"] 60 | 61 | # [Optional] set to rien_output by default 62 | config.output = "compiled" 63 | 64 | # Rien will wait user to input in console when 65 | # finding suspicious already compiled file 66 | # set this to true to turn it off 67 | # [Optional] set to false by default 68 | config.silent = false 69 | 70 | # [Optional] use Dir.tmpdir/rien by default 71 | # aka. /tmp/rien for linux 72 | config.tmpdir = "tmpdir/rien" 73 | 74 | end 75 | ``` 76 | 77 | and then 78 | 79 | ``` 80 | rien -p source -u 81 | ``` 82 | ### Files to be excluded 83 | 84 | #### Read file and eval 85 | 86 | Real world code from [redmine/config/initializers/00-core_plugins.rb 87 | ](https://github.com/redmine/redmine/blob/master/config/initializers/00-core_plugins.rb#L14) 88 | 89 | ```ruby 90 | eval(File.read(initializer), binding, initializer) 91 | ``` 92 | 93 | Compile `lib/plugins/**/init.rb` may lead to a such error in runtime 94 | ``` 95 | init.rb:5:in `
': undefined local variable or method `config' for main:Object (NameError) 96 | ``` 97 | 98 | Use `Rinefile` to exclude `lib/plugins/**/init.rb` 99 | 100 | ### Temporary directory 101 | 102 | Rien uses a `tmpdir` to store intermediate results and timestamps to distinguish them. 103 | 104 | Check your `tmpdir` (`/tmp/rien` for linux by default) to clean unsuccessful build results. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake/testtask' 3 | require 'ci/reporter/rake/minitest' 4 | 5 | Rake::TestTask.new(:minitest) do |t| 6 | t.libs << "lib" 7 | t.libs << "test" 8 | t.test_files = FileList['test/**/*_test.rb'] 9 | end 10 | 11 | task :minitest => 'ci:setup:minitest' 12 | task :default => 'minitest' 13 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | pool: 2 | vmImage: 'Ubuntu 20.04' 3 | 4 | steps: 5 | - task: UseRubyVersion@0 6 | inputs: 7 | versionSpec: '>= 2.7.1' 8 | 9 | - script: | 10 | gem install bundler 11 | bundle install --retry=3 --jobs=4 12 | displayName: 'bundle install' 13 | 14 | - script: bundle exec rake 15 | displayName: 'bundle exec rake' 16 | 17 | - task: PublishTestResults@2 18 | condition: succeededOrFailed() 19 | inputs: 20 | testResultsFiles: '**/reports/TEST-*.xml' 21 | testRunTitle: 'Rien Minitest' 22 | -------------------------------------------------------------------------------- /bin/rien: -------------------------------------------------------------------------------- 1 | #!/bin/ruby 2 | 3 | require "rien" 4 | 5 | Rien::Cli.new.start -------------------------------------------------------------------------------- /lib/rien.rb: -------------------------------------------------------------------------------- 1 | require 'zlib' 2 | 3 | require_relative 'rien/core_ext/string' 4 | 5 | require_relative 'rien/version' 6 | require_relative 'rien/const' 7 | 8 | require_relative 'rien/configurable' 9 | 10 | require_relative 'rien/decoder' 11 | require_relative 'rien/encoder' 12 | 13 | require_relative 'rien/cli' 14 | -------------------------------------------------------------------------------- /lib/rien/cli.rb: -------------------------------------------------------------------------------- 1 | require "optparse" 2 | require "ruby-progressbar" 3 | require "fileutils" 4 | 5 | module Rien::CliHelper 6 | class CliHelperStatus 7 | attr_accessor :silent 8 | 9 | def initialize 10 | @silent = false 11 | end 12 | end 13 | 14 | private def status 15 | @status ||= CliHelperStatus.new 16 | end 17 | 18 | private def encoder 19 | @encoder ||= Rien::Encoder.new 20 | end 21 | 22 | private def wait_user_on_encoded(path) 23 | if path.match(".rbc") && !status.silent 24 | print "\n#{path} maybe have already been encoded, continue? (a/y/n) ".yellow 25 | user_input = STDIN.gets.chomp.downcase 26 | if user_input == "a" 27 | status.silent = true # skip all warnings 28 | elsif user_input == "y" 29 | # ignore 30 | else 31 | abort("Manually abort".red) 32 | end 33 | end 34 | end 35 | 36 | # Notice: 37 | # this path must be relative path, 38 | # or it would break after moving to another directory 39 | private def encode(path) 40 | wait_user_on_encoded(path) 41 | 42 | begin 43 | bytes = encoder.encode_file(path) 44 | rescue Exception => e 45 | abort "\nFailed to encode #{path}, reason:\n#{e.message}".red 46 | end 47 | 48 | bytes 49 | end 50 | 51 | private def export_single_encoded(source, output) 52 | bytes = encode(source) 53 | 54 | File.open("#{output}.rbc", "wb") do |f| 55 | f.write bytes 56 | end 57 | 58 | File.open(output, "w") do |f| 59 | f.write encoder.bootstrap 60 | end 61 | end 62 | 63 | private def replace_with_encoded(path) 64 | bytes = encode(path) 65 | 66 | # Write encoded file 67 | File.open("#{path}.rbc", "wb") do |f| 68 | f.write bytes 69 | end 70 | 71 | # Replace file with bootstrap 72 | FileUtils.rm(path) 73 | File.open(path, "w") do |f| 74 | f.write encoder.bootstrap 75 | end 76 | end 77 | 78 | private def pack_files(paths) 79 | progressbar = ProgressBar.create(:title => "Generating", 80 | :starting_at => 0, 81 | :length => 80, 82 | :total => paths.length, 83 | :smoothing => 0.6, 84 | :format => "%t |%b>>%i| %p%% %a") 85 | 86 | paths.each do |path| 87 | progressbar.log("Compiling: #{path}") 88 | replace_with_encoded(path) # Wirte encoded file and replace the orginal ruby source file with bootstrap 89 | progressbar.increment 90 | end 91 | end 92 | 93 | private def copy_dir(source, output) 94 | FileUtils.mkdir_p output 95 | FileUtils.cp_r File.join(source, "."), output 96 | rescue Exception => e 97 | abort("\nFailed to copy #{source} to #{output}, reason: #{e.message}".red) 98 | end 99 | 100 | private def move_dir(source, output) 101 | FileUtils.mv "#{source}", output 102 | rescue Exception => e 103 | abort("\nFailed to move #{source} to #{output}, reason: #{e.message}".red) 104 | end 105 | 106 | private def pack_all_files_in_directory(source, output, tmpdir) 107 | # Copy to temp workspace 108 | time_stamp = Time.now.strftime("%Y%m%d%H%M%S") 109 | temp_workspace = File.expand_path(time_stamp, tmpdir) 110 | copy_dir(source, temp_workspace) 111 | 112 | # Change to temp workspace 113 | Dir.chdir(temp_workspace) do 114 | # Encode 115 | files = Dir["./**/*.rb"] # pack all files 116 | pack_files(files) 117 | puts "Successed to compile and pack #{source} into #{output}\ntotal #{files.length} file(s)".green 118 | end 119 | 120 | # Clean up 121 | move_dir(temp_workspace, output) 122 | end 123 | 124 | private def use_rienfile_to_pack(source) 125 | # Eval Rienfile 126 | begin 127 | rienfile = File.expand_path("Rienfile", source) 128 | load rienfile 129 | rescue Exception => e 130 | abort "\nFailed to load Rienfile, reason:\n#{e.message}".red 131 | end 132 | 133 | # Configure 134 | status.silent = Rien.config.silent 135 | output = Rien.config.output 136 | tmpdir = Rien.config.tmpdir 137 | 138 | # Copy to temp workspace 139 | time_stamp = Time.now.strftime("%Y%m%d%H%M%S") 140 | temp_workspace = File.expand_path(time_stamp, tmpdir) 141 | copy_dir(source, temp_workspace) 142 | 143 | # Change to temp workspace 144 | source_dir = File.absolute_path(File.dirname(source)) 145 | Dir.chdir(temp_workspace) 146 | 147 | # Encode 148 | files = Rien.config.effective_paths 149 | pack_files(files) 150 | 151 | # Clean up 152 | puts "Successed to compile and pack #{source} into #{output}\n" \ 153 | "using #{rienfile}\n" \ 154 | "total #{files.length} file(s)".green 155 | Dir.chdir(source_dir) 156 | move_dir(temp_workspace, output) 157 | end 158 | end 159 | 160 | class Rien::Cli 161 | include Rien::CliHelper 162 | 163 | def initialize 164 | @options = { 165 | mode: :help, 166 | } 167 | 168 | @parser = OptionParser.new do |opts| 169 | opts.banner = "Usage: rien [options]" 170 | opts.on("-e", "--encode [FILE]", "Encode specific ruby file", String) do |v| 171 | @options[:mode] = :encode 172 | @options[:file] = v 173 | @options[:output] ||= "output.rb" 174 | end 175 | 176 | opts.on("-p", "--pack [DIR]", "Pack ruby directory into encoded files", String) do |v| 177 | @options[:mode] = :pack 178 | @options[:file] = v 179 | @options[:output] ||= "rien_output" 180 | @options[:tmpdir] ||= "/tmp/rien" 181 | end 182 | 183 | opts.on("-o", "--out [FILE/DIR]", "Indicate the output of the encoded file(s)", String) do |v| 184 | @options[:output] = v 185 | end 186 | 187 | opts.on("-u", "--use-rienfile", "Use Rienfile to configure, override other options", String) do 188 | @options[:rienfile] = true 189 | end 190 | 191 | opts.on("-s", "--silent-mode", "Suppress all prompts asking for user input", String) do 192 | @options[:silent] = true 193 | end 194 | 195 | opts.on("-t", "--tmpdir [DIR]", "Select a temp directory to store intermediate results", String) do |v| 196 | @options[:tmpdir] = v 197 | end 198 | end 199 | end 200 | 201 | def start 202 | @parser.parse! 203 | case @options[:mode] 204 | when :encode 205 | source = @options[:file] 206 | output = @options[:output] 207 | status.silent = @options[:silent] 208 | 209 | export_single_encoded(source, output) 210 | 211 | puts "Successed to compile #{source} into #{output} and #{output}.rbc".green 212 | when :pack 213 | source = @options[:file] 214 | abort("\nOnly directory can be packed".red) unless File.directory?(source) 215 | 216 | use_rienfile = @options[:rienfile] 217 | if use_rienfile # Ignore other options from CLI 218 | use_rienfile_to_pack(source) 219 | else # Use options from CLI 220 | output = @options[:output] 221 | tmpdir = @options[:tmpdir] 222 | status.silent = @options[:silent] 223 | 224 | pack_all_files_in_directory(source, output, tmpdir) 225 | end 226 | when :help 227 | puts @parser 228 | else 229 | puts @parser 230 | end 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /lib/rien/configurable.rb: -------------------------------------------------------------------------------- 1 | require 'tmpdir' 2 | 3 | module Rien::Configurable 4 | def self.included(base) 5 | base.extend ClassMethods 6 | end 7 | 8 | module ClassMethods 9 | def config 10 | @config ||= Configuration.new 11 | end 12 | 13 | def configure 14 | yield(config) 15 | end 16 | end 17 | 18 | class Configuration 19 | attr_accessor :includes, :excludes, :output, :tmpdir, :silent 20 | 21 | def initialize 22 | @includes = ["**/*"] # include all paths by default 23 | @output = "rien_output" # final result 24 | @tmpdir = File.expand_path("rien", Dir.tmpdir) # store intermediate results 25 | @silent = false # see cli.rb wait_user_on_encoded 26 | end 27 | 28 | def effective_paths 29 | temp_paths = [] # store ruby files and directories 30 | effective_paths = [] # only ruby files to be compiled 31 | 32 | @includes.each do |path| 33 | path = "./#{path}" 34 | temp_paths += Dir[path] 35 | end 36 | 37 | unless excludes.nil? 38 | @excludes.each do |path| 39 | path = "./#{path}" 40 | temp_paths -= Dir[path] 41 | end 42 | end 43 | 44 | temp_paths.each do |path| 45 | effective_paths.push(path) unless File.directory?(path) || path.match("Rienfile") 46 | end 47 | 48 | effective_paths 49 | end 50 | end 51 | end 52 | 53 | module Rien 54 | include Rien::Configurable 55 | end 56 | -------------------------------------------------------------------------------- /lib/rien/const.rb: -------------------------------------------------------------------------------- 1 | module Rien::Const 2 | COMPILE_OPTION = { 3 | inline_const_cache: true, 4 | instructions_unification: true, 5 | operands_unification: true, 6 | peephole_optimization: true, 7 | specialized_instruction: true, 8 | stack_caching: true, 9 | tailcall_optimization: true, 10 | trace_instruction: false, 11 | }.freeze 12 | end 13 | -------------------------------------------------------------------------------- /lib/rien/core_ext/string.rb: -------------------------------------------------------------------------------- 1 | class String 2 | def colorize(color_code) # ANSI color code 3 | "\e[#{color_code}m#{self}\e[0m" 4 | end 5 | 6 | def red 7 | colorize(31) 8 | end 9 | 10 | def green 11 | colorize(32) 12 | end 13 | 14 | def yellow 15 | colorize(33) 16 | end 17 | 18 | def blue 19 | colorize(34) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/rien/decoder.rb: -------------------------------------------------------------------------------- 1 | class Rien::Decoder 2 | def self.check_version(version) 3 | version == RUBY_VERSION 4 | end 5 | 6 | def self.check_version!(version) 7 | unless self.check_version(version) 8 | puts "Ruby Using: #{RUBY_VERSION}, Version Compiled: #{version}" 9 | exit(1) 10 | end 11 | end 12 | 13 | def self.eval(filename) 14 | file = File.read(filename) 15 | ir_bin = Zlib::Inflate.inflate(file) 16 | ir = RubyVM::InstructionSequence.load_from_binary(ir_bin) 17 | ir.eval 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/rien/encoder.rb: -------------------------------------------------------------------------------- 1 | class Rien::Encoder 2 | def initialize(version=RUBY_VERSION) 3 | @version = version 4 | end 5 | 6 | def encode_file(path) 7 | # RubyVM::InstructionSequence.compile(source, __FILE__, relative_path, options) 8 | bytecode = RubyVM::InstructionSequence.compile(File.read(path), path, path, options: Rien::Const::COMPILE_OPTION) 9 | Zlib::Deflate.deflate(bytecode.to_binary) 10 | end 11 | 12 | def bootstrap 13 | <<-EOL 14 | require 'rien' 15 | 16 | Rien::Decoder.check_version!('#{RUBY_VERSION}') 17 | Rien::Decoder.eval("\#{__FILE__}.rbc") 18 | EOL 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /lib/rien/version.rb: -------------------------------------------------------------------------------- 1 | module Rien 2 | VERSION = '0.0.4' 3 | end 4 | -------------------------------------------------------------------------------- /rien.gemspec: -------------------------------------------------------------------------------- 1 | require './lib/rien/version' 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'rien' 5 | s.version = Rien::VERSION 6 | s.summary = 'Ruby IR Encoding' 7 | s.description = 'Encode your Ruby code for distribution' 8 | 9 | s.platform = Gem::Platform::RUBY 10 | s.required_ruby_version = '>= 2.7.0' 11 | 12 | s.license = 'Apache-2.0' 13 | 14 | s.authors = ['CodeRemixer'] 15 | s.email = ['dsh0416@gmail.com'] 16 | s.homepage = 'https://github.com/coderemixer/rien' 17 | 18 | s.files = Dir['bin/**/*', 'lib/**/*', 'LICENSE', 'README.md'] 19 | s.require_path = 'lib' 20 | 21 | s.bindir = 'bin' 22 | s.executables = ['rien'] 23 | 24 | s.metadata = { 25 | 'bug_tracker_uri' => 'https://github.com/coderemixer/rien/issues' 26 | } 27 | 28 | s.add_runtime_dependency 'ruby-progressbar', '~> 1.9' 29 | s.add_development_dependency 'rake', '~> 13.0.1' 30 | s.add_development_dependency 'minitest', '~> 5.14.1' 31 | s.add_development_dependency 'ci_reporter_minitest', '~> 1.0.0' 32 | end 33 | -------------------------------------------------------------------------------- /test/samples/autoload_gem.rb: -------------------------------------------------------------------------------- 1 | autoload(:Minitest, 'minitest') 2 | puts "require gem minitest:#{Minitest::VERSION}" -------------------------------------------------------------------------------- /test/samples/dir/ruby_in_dir.rb: -------------------------------------------------------------------------------- 1 | puts "This is a ruby file in directory" -------------------------------------------------------------------------------- /test/samples/load_plain.rb: -------------------------------------------------------------------------------- 1 | load("test/samples/plain.rb") -------------------------------------------------------------------------------- /test/samples/not_ruby.rb: -------------------------------------------------------------------------------- 1 | This is not a ruby file! -------------------------------------------------------------------------------- /test/samples/plain.rb: -------------------------------------------------------------------------------- 1 | puts "This is a plain ruby file" 2 | -------------------------------------------------------------------------------- /test/samples/read_eval_plain.rb: -------------------------------------------------------------------------------- 1 | file = File.read("test/samples/plain.rb") 2 | eval(file) -------------------------------------------------------------------------------- /test/samples/require_gem.rb: -------------------------------------------------------------------------------- 1 | require "minitest" 2 | puts "require gem minitest:#{Minitest::VERSION}" 3 | -------------------------------------------------------------------------------- /test/samples/require_relative_plain.rb: -------------------------------------------------------------------------------- 1 | require_relative './plain' -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'rien' -------------------------------------------------------------------------------- /test/unit/cli_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CliHelperTest < Minitest::Test 4 | include Rien::CliHelper 5 | 6 | def setup 7 | FileUtils.cp_r 'test/samples/.', 'test/tmp' 8 | end 9 | 10 | def teardown 11 | FileUtils.remove_dir 'test/tmp' 12 | end 13 | 14 | def test_encode_single_plain 15 | source = 'test/tmp/plain.rb' 16 | 17 | binary = encode(source) 18 | 19 | refute(binary.nil?) 20 | end 21 | 22 | def test_reject_not_ruby_file 23 | source = 'test/tmp/not_ruby.rb' 24 | assert_raises(SystemExit){encode(source)} 25 | end 26 | 27 | def test_export_single_encoded_plain 28 | source = 'test/tmp/plain.rb' 29 | output = 'test/tmp/encoded_plain.rb' 30 | 31 | export_single_encoded(source, output) 32 | 33 | assert_path_exists(output) 34 | end 35 | 36 | def test_replace_with_encoded_plain 37 | source = 'test/tmp/plain.rb' 38 | encoded = 'test/tmp/plain.rb.rbc' 39 | 40 | replace_with_encoded(source) 41 | 42 | assert_path_exists(source) 43 | assert_path_exists(encoded) 44 | end 45 | 46 | def test_pack_all_files_in_directory 47 | source = 'test/tmp/dir' 48 | tmpdir = '/tmp/rien' 49 | output = 'test/tmp/packed' 50 | 51 | pack_all_files_in_directory(source, output, tmpdir) 52 | 53 | assert_path_exists('test/tmp/packed/ruby_in_dir.rb') 54 | assert_path_exists('test/tmp/packed/ruby_in_dir.rb.rbc') 55 | end 56 | 57 | def test_decode_single_plain 58 | source = 'test/tmp/plain.rb' 59 | expected = `ruby #{source}` 60 | 61 | replace_with_encoded(source) 62 | result = `ruby #{source}` 63 | 64 | assert_equal(expected, result) 65 | end 66 | 67 | def test_decode_require_relative_plain 68 | source = 'test/tmp/require_relative_plain.rb' 69 | expected = `ruby #{source}` 70 | 71 | replace_with_encoded(source) 72 | result = `ruby #{source}` 73 | 74 | assert_equal(expected, result) 75 | end 76 | 77 | def test_decode_read_eval_plain 78 | source = 'test/tmp/read_eval_plain.rb' 79 | expected = `ruby #{source}` 80 | 81 | replace_with_encoded(source) 82 | result = `ruby #{source}` 83 | 84 | assert_equal(expected, result) 85 | end 86 | 87 | def test_decode_load_plain 88 | source = 'test/tmp/load_plain.rb' 89 | expected = `ruby #{source}` 90 | 91 | replace_with_encoded(source) 92 | result = `ruby #{source}` 93 | 94 | assert_equal(expected, result) 95 | end 96 | 97 | def test_decode_require_gem 98 | source = 'test/tmp/require_gem.rb' 99 | expected = `ruby #{source}` 100 | 101 | replace_with_encoded(source) 102 | result = `ruby #{source}` 103 | 104 | assert_equal(expected, result) 105 | end 106 | 107 | def test_decode_autoload_gem 108 | source = 'test/tmp/autoload_gem.rb' 109 | expected = `ruby #{source}` 110 | 111 | replace_with_encoded(source) 112 | result = `ruby #{source}` 113 | 114 | assert_equal(expected, result) 115 | end 116 | 117 | end -------------------------------------------------------------------------------- /test/unit/encoder_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class EncoderTest < Minitest::Test 4 | def setup 5 | FileUtils.mkdir_p 'test/tmp' 6 | FileUtils.cp_r 'test/samples/.', 'test/tmp' 7 | end 8 | 9 | def teardown 10 | FileUtils.remove_dir 'test/tmp' 11 | end 12 | 13 | def encoder 14 | @encoder ||= Rien::Encoder.new 15 | end 16 | 17 | def test_encode_single_plain 18 | source = 'test/tmp/plain.rb' 19 | binary = encoder.encode_file(source) 20 | refute(binary.nil?) 21 | end 22 | 23 | def test_reject_not_ruby_file 24 | source = 'test/tmp/not_ruby.rb' 25 | assert_raises(Exception){encoder.encode_file(source)} 26 | end 27 | end --------------------------------------------------------------------------------