Tagged: rubygems Toggle Comment Threads | Keyboard Shortcuts

  • kmitov 7:52 am on March 24, 2021 Permalink |
    Tags: , , , , rubygems,   

    We don’t need ‘therubyracer’ and the libv8 error for compiling native extensions 

    This article is about the error:

    Installing libv8 3.16.14.19 with native extensions
    Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    and the fact that we don’t need and probably you don’t need therubyracer.

    There are two solutions for the above error:

    # To install libv8 with a V8 without precompiling V8 
    $ gem install libv8 -v '3.16.14.19' -- --with-system-v8
    
    # Or to comment therubyracer in your Gemfile and not to use it.
    # gem 'therubyracer'

    We went for the later. Here are more details

    What is libv8 and therubyracer

    From libv8

    V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++. It is used in Chrome and in Node.js, among others.

    What therubyracer does is to embeds the V8 Javascript Interpreter into ruby.

    In Rails there is the execjs gem used in assets precompilations.

    ExecJS lets you run JavaScript code from Ruby. It automatically picks the best runtime available to evaluate your JavaScript program, then returns the result to you as a Ruby object.

    https://github.com/sstephenson/execjs

    This means that execjs needs a JS runtime and it uses the one provided by therubyracer, libv8 and V8. But it can use other runtimes as well, like Node.

    What is the libv8 compilation error?

    The libv8 error is because it can not compile V8 on the machine. V8 is developed in C++. To successfully compile it the machine must have all the dependencies needed for compilation. You can try to compile it from scratch or you could use an already compiled one. Compiling from scratch takes time to resolve all the dependencies.

    But we didn’t need therubyracer

    Turns out that in this specific platform we don’t need therubyracer, because we already have Node because of webpacker/webpack and we have it on all the machines and we have it on production on Heroku.

    therubyracer is also discouraged on Heroku –

    If you were previously using therubyracer or therubyracer-heroku, these gems are no longer required and strongly discouraged as these gems use a very large amount of memory.

    A version of Node is installed by the Ruby buildpack that will be used to compile your assets.

    https://devcenter.heroku.com/articles/rails-asset-pipeline#therubyracer

    therubyracer is great for starting quickly especially when there is no node. But considering there there was node on all of our machines we just don’t need therubyracer.

    As a result the slug size on heroku was reduced from 210M to 195.9M. No bad. I guess the memory consumption will also be reduced.

    remote: -----> Compressing...        
    remote:        Done: 210M        
    remote: -----> Launching...        
    remote:        Released v5616     
    
    remote: -----> Compressing...        
    remote:        Done: 195.9M        
    remote: -----> Launching...        
    remote:        Released v5617       

    How did we came about this error?

    Every Wednesday we do an automatic bundle update to see what’s new and update all the gems (of course if the tests pass). This Wednesday the build failed, because of the libv8 error and native compilation.

    It was just an unused dependency left in the Gemfile.

     
  • kmitov 5:23 pm on May 21, 2020 Permalink |
    Tags: , , , , , , rubygems,   

    With assets in a rails engine it could be Gemfile vs gemspec dependency that is messing it up 

    You have a rails engine. It has an asset (like a scss file). You include this engine in another engine. How do you test that the assets is correctly resolved?

    There is a moment when you simply can not understand where and how the asset is resolved/not resolved. The solution actually is in what are the Gemfile and .gemspec for a rails engine. Both of them could describe dependencies, but they contain different things.

    The whole premise of the situation seems strange until you get to building a rails engine that would contain the “theme” of your app. We go in this situation with two of our platforms – FLLCasts.com and 3DAssemblyInstructions.com. We wanted to have different layouts in different gems that are providing different look an feel- separate the main layout from dashboard and from main. We also wanted to have this layouts in a different gems so that we could release them with different versions and test them as separate gems in separate builds.

    Let’s get into this step by step tutorial and by the end I am sure you would know much about how assets are resolved, packed and testes when they are within a rails. Again I am writing this tutorial to help spread the knowledge internally, but the problem is so common that it might be of interest to the community as a whole.

    Case 1 – simple app with an asset

    Just to get the understanding correctly and to have a base we would create a simple rails app with an asset and start it. I am using rails 6 and ruby 2.6.5

    $ rails new simple_assets_app
    $ cd simple_asset_app
    # Create a new asset.scss file with some content
    $ echo "asset_style { background-color: white}" > app/assets/stylesheets/asset.scss
    $ rails s
    => Booting Puma
    => Rails 6.0.3.1 application starting in development 
    => Run `rails server --help` for more startup options
    Puma starting in single mode...
    * Version 4.3.5 (ruby 2.6.5-p114), codename: Mysterious Traveller
    * Min threads: 5, max threads: 5
    * Environment: development
    * Listening on tcp://127.0.0.1:3000
    * Listening on tcp://[::1]:3000
    Use Ctrl-C to stop
    
    

    In another terminal request the asset

    # Request the asset and dislay its content. Everything works. Perfect
    $ curl localhost:3000/assets/asset.scss
    asset_style { background-color: white}

    Case 2 – engine with an asset

    Now let’s build a rails plugin that would hold this asset

    $ rails plugin new simple_asset_engine --full
    $ cd simple_asset_engine/
    $ echo "asset_style { background-color: white}" > app/assets/stylesheets/asset.scss
    # You have to fix the TODOs in the gemspec. After you are ready do 
    $ rails s -p 3001
    => Booting WEBrick
    => Rails 6.0.3.1 application starting in development http://localhost:3001
    => Run `rails server --help` for more startup options
    [2020-05-21 19:48:51] INFO  WEBrick 1.4.2
    [2020-05-21 19:48:51] INFO  ruby 2.6.5 (2019-10-01) [x86_64-linux]
    [2020-05-21 19:48:51] INFO  WEBrick::HTTPServer#start: pid=13394 port=3001
    

    Now that the port is different.

    Request the asset

    # Nothing strange here. Asset is delivered as expected.
    $ curl localhost:3001/assets/asset.scss
    asset_style { background-color: white}
    

    Conclusion – engine, no engine the asset is delivered.

    Case 3 – require simple_asset_engine in another engine

    This could be a requirement. It has happen to us. It is a valid case. It is rather strange to have one engine require another, but we are all grown ups here…

    $ rails plugin new host_engine --full
    # Fix todos in .gemspec
    
    # Add a dependency to simple_asset_engine. Asset should be found, rigth
    $ echo 'gem "simple_asset_engine", path: "~/axles/tmp/simple_asset_engine"' >> Gemfile
    
    # install gems - mainly the simple_asset_engine
    $ bundle install
    
    # Start server. Note the different port
    $ rails s -p 4010
    
    

    Request asset

    $ curl localhost:4010/assets/asset.scss
    asset_style { background-color: white}

    Asset is found.

    That’s the power of rails. It just works. You can have an asset in an app, asset in an engine, asset in an engine, required in another engine and the asset is always found. Except…

    This might sound as a conclusion, because everything works. Until you get to production, which is actually the only time it matters if anything works, but that’s another story.

    Our current host_engine depends on a gem with a local path in the local filesystem.

    host_engine: $ cat Gemfile
    source 'https://rubygems.org'
    git_source(:github) { |repo| "https://github.com/#{repo}.git" }
    
    # Declare your gem's dependencies in host_engine.gemspec.
    # Bundler will treat runtime dependencies like base dependencies, and
    # development dependencies will be added by default to the :development group.
    gemspec
    
    # Declare any dependencies that are still in development here instead of in
    # your gemspec. These might include edge Rails or gems from your path or
    # Git. Remember to move these dependencies to your gemspec before releasing
    # your gem to rubygems.org.
    
    # To use a debugger
    # gem 'byebug', group: [:development, :test]
    gem "simple_asset_engine", path: "~/axles/tmp/simple_asset_engine"

    This is something that we can not ship to production. That’s why we pack “simple_asset_engine” as a gem and place this gem in a gemrepo. This is not part of the tutorial so I will simulate it.

    We should also modify the host_engine.gemspec to depend on simple_asset_engine. We add a dependency.

    # in host_engine.gemspec we add
    
    spec.add_dependency "simple_asset_engine"
    

    This means – ‘add a dependency to “simple_asset_engine”‘. Our gem will host_engine will depend on ‘simple_asset_engine’, but and here is a but, but only for ‘production’ environment. So this means – “production”. To simulate the fact that we don’t have this gem in a repo we would change the dependency in the Gemfile to be only for production.

    # This means the same as spec.add_dependency 'simple_asset_engine' It add the gem as a dependency to production. We are doing it like this to simulate that we have 'spec.add_dependency' and the gem is coming from a gems repo
    
    gem "simple_asset_engine", group: [:production], path: "~/axles/tmp/simple_asset_engine"

    Now as you do request the asset a strange error occurs:

    $ curl localhost:4010/assets/asset.scss
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8" />
      <title>Action Controller: Exception caught</title>
        ....
       <div id="Framework-Trace-0" style="display: none;">
          <code style="font-size: 11px;">
              <a class="trace-frames trace-frames-0" data-exception-object-id="47236752082100" data-frame-id="0" href="#">
                actionpack (6.0.3.1) lib/action_dispatch/middleware/debug_exceptions.rb:36:in `call'
              </a>
              <br>
              <a class="trace-frames trace-frames-0" data-exception-object-id="47236752082100" data-frame-id="1" href="#">
                actionpack (6.0.3.1) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
              </a>
              <br>
              <a class="trace-frames trace-frames-0" data-exception-object-id="47236752082100" data-frame-id="2" href="#">
                railties (6.0.3.1) lib/rails/rack/logger.rb:37:in `call_app'
              </a>
    ...
    </div>
    </body>
    </html>
    

    In the log you see

    ActionController::RoutingError (No route matches [GET] "/assets/asset.scss"):
      
    actionpack (6.0.3.1) lib/action_dispatch/middleware/debug_exceptions.rb:36:in `call'
    actionpack (6.0.3.1) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
    railties (6.0.3.1) lib/rails/rack/logger.rb:37:in `call_app'
    railties (6.0.3.1) lib/rails/rack/logger.rb:26:in `block in call'
    activesupport (6.0.3.1) lib/active_support/tagged_logging.rb:80:in `block in tagged'
    activesupport (6.0.3.1) lib/active_support/tagged_logging.rb:28:in `tagged'
    activesupport (6.0.3.1) lib/active_support/tagged_logging.rb:80:in `tagged'
    railties (6.0.3.1) lib/rails/rack/logger.rb:26:in `call'
    sprockets-rails (3.2.1) lib/sprockets/rails/quiet_assets.rb:11:in `block in call'
    activesupport (6.0.3.1) lib/active_support/logger_silence.rb:36:in `silence'
    activesupport (6.0.3.1) lib/active_support/logger.rb:64:in `block (3 levels) in broadcast'
    activesupport (6.0.3.1) lib/active_support/logger_silence.rb:36:in `silence'
    activesupport (6.0.3.1) lib/active_support/logger.rb:62:in `block (2 levels) in broadcast'
    sprockets-rails (3.2.1) lib/sprockets/rails/quiet_assets.rb:11:in `call'
    actionpack (6.0.3.1) lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
    actionpack (6.0.3.1) lib/action_dispatch/middleware/request_id.rb:27:in `call'
    rack (2.2.2) lib/rack/method_override.rb:24:in `call'
    rack (2.2.2) lib/rack/runtime.rb:22:in `call'
    activesupport (6.0.3.1) lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
    actionpack (6.0.3.1) lib/action_dispatch/middleware/executor.rb:14:in `call'
    actionpack (6.0.3.1) lib/action_dispatch/middleware/static.rb:126:in `call'
    rack (2.2.2) lib/rack/sendfile.rb:110:in `call'
    actionpack (6.0.3.1) lib/action_dispatch/middleware/host_authorization.rb:82:in `call'
    railties (6.0.3.1) lib/rails/engine.rb:527:in `call'
    rack (2.2.2) lib/rack/handler/webrick.rb:95:in `service'
    

    It is basically telling us that an error occurred. An error also should occur. It is logical, it is the right thing to do, but it can take hours to debug and understand this.

    The error occurs because we are starting the rails server in ‘development’ environment and the dependency to ‘simple_asset_engine’ is only for production.

    Case 3.1 – change to production and development

    # Change host_engine/Gemfile to have
    
    gem "simple_asset_engine", group: [:production, :development], path: "~/axles/tmp/simple_asset_engine"
    

    Restart server on port 4010.

    host_engine: $ rails s -p 4010
    => Booting WEBrick
    => Rails 6.0.3.1 application starting in development http://localhost:4010
    => Run `rails server --help` for more startup options
    [2020-05-21 20:14:55] INFO  WEBrick 1.4.2
    [2020-05-21 20:14:55] INFO  ruby 2.6.5 (2019-10-01) [x86_64-linux]
    [2020-05-21 20:14:55] INFO  WEBrick::HTTPServer#start: pid=14167 port=4010

    Request the asset

    $ curl localhost:4010/assets/asset.scss
    asset_style { background-color: white}

    Now it is time for conclusion

    spec.add_dependency in a gemspec gives you a dependency for production. There is a ‘spec.add_development_dependency’ that exists, but there are great discussion about it here https://github.com/rubygems/rubygems/issues/1104 Read more there. Really. Read more.

    But as we are trying to test assets separately in an engine it is important to understand what Gemfile and gemspec could be used for. If an asset is not found from a dependency of a gemspec, probably the whole dependency is required for a different environment. In the same time if the dependency contains only assets, it might be difficult to get what is happening at first sight and is it your fault, or that the F..k is sprockets doing or event what the bigger f..k is webpacker doing. But for this case it is just plain old ruby dependencies that are messing us up.

     
c
compose new post
j
next post/next comment
k
previous post/previous comment
r
reply
e
edit
o
show/hide comments
t
go to top
l
go to login
h
show/hide help
shift + esc
cancel