Gastón Ramos

"La mayoría de las gaviotas no se molesta en aprender sino las normas de vuelo más elementales: como ir y volver entre playa y comida. Para la mayoría de las gaviotas, no es volar lo que importa, sino comer. Para esta gaviota, sin embargo, no era comer lo que le importaba, sino volar. Más que nada en el mundo, Juan Salvador Gaviota amaba volar".

Category: rubygems

Panthro, el proxy cache para rubygems.

Hace un tiempo atrás se me ocurrió escribir un proxy cache para las gemas, lo hice un poco para aprender y otro poco a para acelerar un poco el uso del rubygems en un lugar común de trabajo, como por ejemplo una oficina. La idea es que cuando instalamos un gema por primera vez esta se baja desde rubygems y se guarda en el cache en dónde panthro está corriendo, específicamente en ~/.panthro/gems, luego cuando necesitemos instalar la misma gema desde otra máquina en la misma red y que tenga configurado panthro como source de rubygems (veáse el comando `gem source`) esta y todas sus dependecias no necesitan ser descargadas dado que ya están en el cache.

Panthro

Hay algunas cosas que me falta implementar y es expirar es el cache de los archivos latest_specs.4.8.gz, prerelease_specs.4.8.gz y specs.4.8.gz entre otras que están detalladas en el TODO del proyecto.

https://github.com/gramos/panthro

Es bueno aclarar que hasta ahora panthro tiene 63 líneas de código sin contar los tests. Además para poder testear que tan rápido es Panthro y compararlo contra usar directamente rubygems hice una gema muy chiquita que se llama rumb y que podés mirar acá: https://github.com/gramos/rumb esta tiene 57 líneas de código contando el texto del ayuda.

Advertisements

Desinstalando rbenv de ruby

Desde hace algunos días que estoy haciendo unas pruebas con algunas gemas, hoy instalé geminabox y cuando quiero probarlo como dice en el README, obtengo un LoadError: ‘geminabox’ not found, a lo cual pensé not found? pero si lo acabo de instalar…
Después de revisar un poco, ejecuto `gem env` y veo que tengo un quilombo con los GEM_PATH y GEM_HOME dado que tengo instalado rbenv ( https://github.com/sstephenson/rbenv ) para manejar las versiones de ruby y además de rbenv tambien tengo el plugin communal ( https://github.com/tpope/rbenv-communal-gems ) para compartir las gemas entre las versiones compatibles de ruby.
Además tengo instalado el paquete rybgems-integration que también hace cosas con rubygems, o sea un kilombo. Después de hacer varias pruebas y ver que las cosas no funcionan muy bien, decidí desinstalar rbenv por ahora y me encontré con no hay un `rbenv uninstall` Así que esta es la receta para hacerlo:

Borro todo el contenido del directorio rbenv:

rm -rf ~/.rbenv

Elimino todo lo agregado en los archivos del shell:

grep rbenv ~/.bashrc ~/.bash_profile ~/.zshrc /etc/profile /etc/profile.d/*

Luego de esto hago un `gem env` y veo que todavía sigo teniendo los path the rbenv:

Econtré que tenía un GEM_HOME agregado en el archivo de conf de rubygems y me decidí a borrar este archivo:

rm ~/.gemrc

después de esto tengo un máquina en un ambiente de rubgems más o menos “limpio”, más adelante instalaré rbenv de nuevo.

Bless.

Can’t install RMagick 2.13.1. Can’t find MagickWand.h

Estuve tratando de instalar rmagick 2.13.1 en debian wheezy, y al ejecutar el gem install

me tiraba el siguiente error:


Installing rmagick (2.13.1) with native extensions
Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension.

        /home/gramos/.rbenv/versions/1.9.2-p320/bin/ruby extconf.rb
checking for Ruby version >= 1.8.5... yes
checking for /usr/bin/gcc... yes
checking for Magick-config... yes
checking for ImageMagick version >= 6.4.9... yes
checking for HDRI disabled version of ImageMagick... yes
checking for stdint.h... yes
checking for sys/types.h... yes
checking for wand/MagickWand.h... no

Can't install RMagick 2.13.1. Can't find MagickWand.h.
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers.  Check the mkmf.log file for more
details.  You may need configuration options.

Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include
    --without-opt-include=${opt-dir}/include
    --with-opt-lib
    --without-opt-lib=${opt-dir}/lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=/home/gramos/.rbenv/versions/1.9.2-p320/bin/ruby


Gem files will remain installed in /home/gramos/.rbenv/versions/1.9.2-p320/lib/ruby/gems/1.9.1/gems/rmagick-2.13.1 for inspection.
Results logged to /home/gramos/.rbenv/versions/1.9.2-p320/lib/ruby/gems/1.9.1/gems/rmagick-2.13.1/ext/RMagick/gem_make.out

Se soluciona instalando la siguiente biblioteca:


sudo apt-get install libmagick++-dev

ya hora sí….


gem install rmagick -v '2.13.1'
Building native extensions.  This could take a while...
Successfully installed rmagick-2.13.1
1 gem installed
Installing ri documentation for rmagick-2.13.1...
Installing RDoc documentation for rmagick-2.13.1...

Espero que les sea útil!

¿Cómo funciona RubyGems?

Desde hace un tiempo que estoy interesado en conocer un poco más en detalle como funciona rubygems, así que hoy decidí comenzar a aprender más sobre rubygems y cómo las gemas son cargadas en memoria.

Bueno, comencemos:
Entro al intérprete interactivo de ruby e intento requerir la bioblioteca activemodel:


ruby-1.9.2-p136 :001 > Gem.loaded_specs
 => {}
ruby-1.9.2-p136 :002 > require 'active_model'
 => true 
ruby-1.9.2-p136 :003 > Gem.loaded_specs
 => {"activemodel"=>#<Gem::Specification name=activemodel version=3.0.4>, 
"activesupport"=>#<Gem::Specification name=activesupport version=3.0.4>, 
"builder"=>#<Gem::Specification name=builder version=2.1.2>, 
"i18n"=>#<Gem::Specification name=i18n version=0.5.0>}

Cómo podemos observar aparecen varias gemas nuevas cargadas, active_model y sus dependencias, pero ¿cómo es que esto se lleva cabo?

Bien si nos fijamos en /lib/rubygems.rb en el repo de rubygems al final de todo hace esto:


require 'rubygems/custom_require' unless Gem::GEM_PRELUDE_SUCKAGE

y si vamos al archivo lib/rubygems/custom_require.rb vemos que lo hace es redefinir el método require de Kernel, y activa la gema de esta forma:


Gem.try_activate(path)

podemos probar esto directamente en el intérprete:


ruby-1.9.2-p136 > Gem.try_activate 'active_resource'
=> true
ruby-1.9.2-p136 :007 > Gem.loaded_specs['activeresource']
 => #<Gem::Specification name=activeresource version=3.0.4> 


Entonces vamos de nuevo al archivo rubygems.rb y miramos como es que funciona Gem.try_activate:

Lo primero que hace es buscar la gema que estamos intentando requerir, y lo hace así:


 spec = Gem.searcher.find path

Gem.searcher es una instancia de Gem::GemPathSearcher entre otras cosas tiene la lista de todas las gemspecs instaldas para después poder hacér búsqueda con Gem::GemPathSearcher#find. Entonces siguiendo con Gem.try_activate, si encuentra alguna coincidencia ejecuta

Gem.activate spec.name, "= #{spec.version}"

como podemos ver saca la versión de la spec encontrada (que es la más alta instalada), entonces ahora vamos a ver

Gem.activate

Éste método retorna true si la gema fué activada, false si ya está cargada o una excepción en cualquier otro caso (no se pudo cargar por algún motivo). La manera en que activa la gema es agregando el path de la misma al $LOAD_PATH de ruby.


 def self.activate(gem, *requirements)
   if requirements.last.is_a?(Hash)
      options = requirements.pop
   else
      options = {}
   end
  
   # Esta línea parsea los requerimientos que vienen como argumento
   # y si no viene ninguno pone los defaults, los requerimientos no son
   # otra cosa que restricciones de la versión de la gema,
   # el requerimiento por defecto es >= 0

   requirements = Gem::Requirement.default if requirements.empty?
   
   # Básicamente Gem::Dependency es un contenedor de 
   # Gem name, requirement

   dep = Gem::Dependency.new(gem, requirements)

   
   sources = options[:sources] || []

   # creo que esta línea require especial antención 
   # (veremos los detalles más adelante) pero por ahora nos 
   # alcanza con saber que Gem._unresolved crea una lista 
   # de objetos Gem::DependencyList (que primero está vacía) 
   # de dependecias sin resolver
   
   matches = _unresolved.find_all { |spec| spec.satisfies_requirement? dep }

   if matches.empty? then
      matches = Gem.source_index.find_name(dep.name, dep.requirement)

      # Si no ecuentra la gema buscando con el nombre y los 
      # requerimientos reporta el error
      report_activate_error(dep) if matches.empty?
      
      # Si la gema ya está cargada (cualquier versión)
      if @loaded_specs[dep.name] then
        # This gem is already loaded.  If the currently loaded gem is not in the
        # list of candidate gems, then we have a version conflict.
        existing_spec = @loaded_specs[dep.name]
        
        # Pregunta si la gema que está cargada es la misma versión 
        # que la  quiero activar
        unless matches.any? { |spec| spec.version == existing_spec.version } then
          sources_message = sources.map { |spec| spec.full_name }
          stack_message = @loaded_stacks[dep.name].map { |spec| spec.full_name }

          msg = "can't activate #{dep} for #{sources_message.inspect}, "
          msg << "already activated #{existing_spec.full_name} for "
          msg << "#{stack_message.inspect}"

          e = Gem::LoadError.new msg
          e.name = dep.name
          e.requirement = dep.requirement

          raise e
        end
        
        # La gema que quiero cargar ya está cargada
        return false
      end
    end
    
    # La gema no está cargada entonces activa la última versión
    spec = matches.last

    conf = spec.conflicts
    unless conf.empty? then
      why = conf.map { |k,v| "#{k} depends on #{v.join(", ")}" }.join ", "

      raise LoadError, "Unable to activate #{spec.full_name}, but #{why}"
    end

    return false if spec.loaded?

    spec.loaded = true
    @loaded_specs[spec.name]  = spec
    @loaded_stacks[spec.name] = sources.dup

    spec.runtime_dependencies.each do |spec_dep|
      next if Gem.loaded_specs.include? spec_dep.name
      specs = Gem.source_index.search spec_dep, true

      _unresolved.add(*specs)
    end

    current = Hash.new { |h, name| h[name] = Gem::Dependency.new name }
    Gem.loaded_specs.each do |n,s|
      s.runtime_dependencies.each do |s_dep|
        current[s_dep.name] = current[s_dep.name].merge s_dep
      end
    end

    _unresolved.remove_specs_unsatisfied_by current

    require_paths = spec.require_paths.map do |path|
      File.join spec.full_gem_path, path
    end

    # gem directories must come after -I and ENV['RUBYLIB']
    insert_index = load_path_insert_index

    if insert_index then
      # gem directories must come after -I and ENV['RUBYLIB']
      $LOAD_PATH.insert(insert_index, *require_paths)
    else
      # we are probably testing in core, -I and RUBYLIB don't apply
      $LOAD_PATH.unshift(*require_paths)
    end

    return true
  end


Si alguien encuentra algún en error en mi seguimiento hasta acá le pido que haga un comentario en este post, Continuará…