2013-07-01 11 views
10

Pracuję nad klejnotem, który jest obecnie czystym Ruby, ale opracowałem też szybszy wariant C dla jednej z funkcji. Funkcja jest użyteczna, ale czasami powolna, w czystej Ruby. Powolność będzie miała wpływ tylko na niektórych potencjalnych użytkowników (zależy od tego, jakich funkcji potrzebują i w jaki sposób ich używają), więc warto mieć klejnot z pełnym wdziękiem powrotem do funkcji tylko w języku Ruby, jeśli nie może on skompilować się w systemie docelowym.Powrót do oryginalnych rozszerzeń natywnych do Ruby, jeśli nie jest obsługiwany w instalacji gem

Chciałbym zachować warianty Ruby i C obiektu w jednym klejnocie i zapewnić najlepsze (to znaczy najszybsze) doświadczenie z klejnotu podczas instalacji. To pozwoliłoby mi wspierać najszerszy zestaw potencjalnych użytkowników z jednego mojego projektu. Pozwoliłoby to również, aby klejnoty i projekty zależne od innych osób używały najlepszej dostępnej zależności w systemie docelowym, w przeciwieństwie do wersji o najniższym wspólnym mianowniku dla zgodności.

chciałbym oczekiwać require awaryjnej przy starcie pojawiają się w głównym lib/foo.rb pliku po prostu jak ten:

begin 
    require 'foo/foo_extended' 
rescue LoadError 
    require 'foo/ext_bits_as_pure_ruby' 
end 

Jednak nie wiem jak dostać się instalacja gem sprawdzić (lub spróbować fail) w przypadku obsługi natywnego rozszerzenia, aby klejnot był instalowany poprawnie, niezależnie od tego, czy może on zbudować 'foo_extended'. Kiedy badałem, jak to zrobić, znalazłem głównie dyskusje sprzed kilku lat, np. http://permalink.gmane.org/gmane.comp.lang.ruby.gems.devel/1479 i http://rubyforge.org/pipermail/rubygems-developers/2007-November/003220.html co sugeruje, że klejnoty Ruby nie obsługują tej funkcji. Nic nowego, więc mam nadzieję, że ktoś z SO ma trochę więcej aktualnej wiedzy?

Moje idealne rozwiązanie byłoby sposobem na wykrycie, przed rozpoczęciem kompilacji rozszerzenia, że ​​docelowa Ruby nie obsługuje (lub po prostu nie chce, na poziomie projektu) C natywnych rozszerzeń. Ale także mechanizm try/catch byłby OK, gdyby nie był zbyt brudny.

Czy to możliwe, jeśli tak, w jaki sposób? Czy też zaleca się opublikowanie dwóch wariantów klejnotów (np. foo i foo_ruby), które znajduję, kiedy szukam, wciąż aktualną najlepszą praktykę?

+0

Dwa klejnoty są w porządku, np. Klejnot json występuje w dwóch wariantach: ['json'] (https://rubygems.org/gems/json) (z rozszerzeniem C) i [' json_pure'] (https://rubygems.org/gems/json_pure) (czysty Rubin). – Stefan

+0

@Stefan: W rozmowie, którą połączyłem, autor/maintaner 'json' i' json_pure' najwyraźniej wolałby inaczej. Oprócz dodatkowej pracy, która publikuje dwa warianty klejnotu, zależne projekty, które same mogą być zależne od czegoś innego, w końcu muszą używać najniższego wspólnego mianownika lub muszą również zapewnić dwa warianty tylko po to, aby zarządzać zależnościami, które nie kodują. . Nie nazwałbym tego "w porządku", ale jeśli jest to najlepsze z możliwych, to wszystko, co mogę zrobić. –

+0

@Stefan: Zdecydowanie zamiar Neila związany z projektem jest właściwy. Jego pytanie jest wspaniałe i bardzo ważne, sam jestem zainteresowany odpowiedzią, proszę, upomnij się o to. –

Odpowiedz

1

To jest mój najlepszy wynik próbuje odpowiedzieć na moje własne pytanie na bieżąco. Wydaje się działać dla JRuby (testowany w Travis i na mojej lokalnej instalacji pod RVM), co było moim głównym celem. Jednak byłbym bardzo zainteresowany potwierdzeń nim pracujących w innych środowiskach, a za każdym wejściem, w jaki sposób uczynić go bardziej ogólne i/lub wytrzymałość:


Kod instalacja perełka oczekuje Makefile jako wyjście z extconf.rb , ale nie ma zdania na temat tego, co to powinno zawierać.W związku z tym extconf.rb może zdecydować o utworzeniu nic nie robićMakefile, zamiast wywoływania create_makefile z mkmf. W praktyce może wyglądać następująco:

ext/foo/extconf.rb

can_compile_extensions = false 
want_extensions = true 

begin 
    require 'mkmf' 
    can_compile_extensions = true 
rescue Exception 
    # This will appear only in verbose mode. 
    $stderr.puts "Could not require 'mkmf'. Not fatal, the extensions are optional." 
end 


if can_compile_extensions && want_extensions 
    create_makefile('foo/foo') 

else 
    # Create a dummy Makefile, to satisfy Gem::Installer#install 
    mfile = open("Makefile", "wb") 
    mfile.puts '.PHONY: install' 
    mfile.puts 'install:' 
    mfile.puts "\t" + '@echo "Extensions not installed, falling back to pure Ruby version."' 
    mfile.close 

end 

Jak sugeruje w pytaniu, odpowiedź ta wymaga również logikę wczytania kodu awaryjnej Ruby w głównym biblioteka:

lib/foo.rb (fragment)

begin 
    # Extension target, might not exist on some installations 
    require 'foo/foo' 
rescue LoadError 
    # Pure Ruby fallback, should cover all methods that are otherwise in extension 
    require 'foo/foo_pure_ruby' 
end 

Podążanie tą trasą również wymaga trochę żonglowania zadaniami rake, więc domyślne zadanie rake nie próbuje kompilować na Rubiach, które testujemy, które nie mają możliwości kompilacji rozszerzeń:

Rakefile (fragmenty)

def can_compile_extensions 
    return false if RUBY_DESCRIPTION =~ /jruby/ 
    return true 
end 

if can_compile_extensions 
    task :default => [:compile, :test] 
else 
    task :default => [:test] 
end 

Zanotuj Rakefile część nie musi być całkowicie generycznych, to po prostu musi pokrycie znanych środowisk chcemy lokalnie budować i testować na gem (np wszystkie cele Travis).

Zauważyłem jedną irytację. Domyślnie zobaczysz komunikat Ruby Gems Building native extensions. This could take a while... i nic nie wskazuje na to, że kompilacja rozszerzenia została pominięta. Jeśli jednak wywołasz instalator pod numerem gem install foo --verbose, zobaczysz komunikaty dodane do extconf.rb, więc nie jest tak źle.

1

Oto myśl, na podstawie informacji z http://guides.rubygems.org/c-extensions/ i http://yorickpeterse.com/articles/hacking-extconf-rb/.

Wygląda na to, że możesz umieścić logikę w extconf.rb. Na przykład, zapytanie stałej RUBY_DESCRIPTION i określić, czy jesteś w Ruby, który obsługuje natywne rozszerzenia:

$ irb 
jruby-1.6.8 :001 > RUBY_DESCRIPTION 
=> "jruby 1.6.8 (ruby-1.8.7-p357) (2012-09-18 1772b40) (Java HotSpot(TM) 64-Bit Server VM  
    1.6.0_51) [darwin-x86_64-java]" 

Można więc spróbować czegoś podobnego owinąć kod w extconf.rb w warunkowa (w extconf.rb):

unless RUBY_DESCRIPTION =~ /jruby/ do 

    require 'mkmf' 

    # stuff  
    create_makefile('my_extension/my_extension') 

end 

Oczywiście, będziemy chcieli bardziej wyrafinowane logiki, chwytając parametry przekazane na „gem install”, itp

+0

Ten kod w 'extconf.rb' nie działał. Ale podobny kod w gemspec wokół zależności od instalacji rake i deklarowania rozszerzenia wydaje się działać zgodnie z wymaganiami. Muszę się z nim trochę więcej bawić, ale jest obiecująca. –

+0

Edycja gemspec pozwoliła mi ponownie przetestować klejnot na JRuby, a ja miałem wszystkie swoje zadania rake'u pracujące we wszystkich testowych rubinach, ale ostatecznie to też nie działało. Problem polega na tym, że '.gemspec' jest przetwarzany zbyt wcześnie, więc działa tylko wtedy, gdy kompilacja klejnotu jest wykonywana w tym samym celu Ruby. Jednak 'extconf.rb' jest przetwarzany zbyt późno, jeśli gem zawiera' extconf.rb' system docelowy może go odrzucić, zanim uruchomiona zostanie jakakolwiek logika. –

+0

Dzięki za wskazówki, doprowadziły do ​​tego, co uważam za realną odpowiedź. Jednak jest w tym coś więcej niż wykrywanie celów, które się nie kompilują, więc dodałem moje wnioski jako nową odpowiedź. –

Powiązane problemy