2013-07-01 27 views
10

Tôi đang phát triển một gem, hiện là Ruby thuần túy, nhưng tôi cũng đang phát triển một biến thể C nhanh hơn cho một trong các tính năng. Tính năng này có thể sử dụng được, nhưng đôi khi chậm, trong Ruby thuần khiết. Sự chậm chạp sẽ chỉ ảnh hưởng đến một số người dùng tiềm năng (phụ thuộc vào các tính năng họ cần và cách họ sử dụng chúng), do đó, nó có ý nghĩa khi có sẵn đá quý với dự phòng duyên dáng đối với các hàm chỉ có Ruby nếu nó không thể biên dịch trên hệ thống đích.Native extensions fallback thành Ruby tinh khiết nếu không được hỗ trợ trên gem install

Tôi muốn duy trì các biến thể Ruby và C của đối tượng địa lý trong một đá quý và cung cấp trải nghiệm tốt nhất (tức là nhanh nhất) từ đá quý khi cài đặt. Điều đó sẽ cho phép tôi hỗ trợ nhóm người dùng tiềm năng rộng nhất từ ​​một dự án duy nhất của tôi. Nó cũng sẽ cho phép các đá quý và dự án phụ thuộc của người khác sử dụng sự phụ thuộc tốt nhất có sẵn trên một hệ thống đích, trái ngược với một phiên bản mẫu số chung thấp nhất để tương thích.

tôi mong chờ các require để dự phòng trong thời gian chạy để xuất hiện trong tập tin chính lib/foo.rb chỉ đơn giản như thế này:

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

Tuy nhiên, tôi không biết làm thế nào để có được quá trình cài đặt đá quý để kiểm tra (hoặc cố gắng và không) cho hỗ trợ tiện ích mở rộng gốc để đá quý cài đặt chính xác cho dù nó có thể xây dựng 'foo_extended' hay không. Khi tôi nghiên cứu cách thực hiện điều này, tôi chủ yếu tìm thấy các cuộc thảo luận từ vài năm trước, ví dụ: http://permalink.gmane.org/gmane.comp.lang.ruby.gems.devel/1479http://rubyforge.org/pipermail/rubygems-developers/2007-November/003220.html có nghĩa là đá quý Ruby không thực sự hỗ trợ tính năng này. Không có gì gần đây mặc dù, vì vậy tôi hy vọng một người nào đó trên SO có một số kiến ​​thức up-to-date?

Giải pháp lý tưởng của tôi sẽ là cách phát hiện, trước khi thử xây dựng phần mở rộng, mục tiêu mà Ruby không hỗ trợ (hoặc có thể không muốn ở cấp độ dự án). Nhưng cũng có, một cơ chế thử/nắm bắt sẽ ổn nếu không quá bẩn.

Điều này có thể thực hiện được không? Hoặc là lời khuyên để có hai biến thể đá quý được xuất bản (ví dụ: foofoo_ruby), mà tôi đang tìm kiếm khi tôi tìm kiếm, vẫn là phương pháp hay nhất hiện tại?

+0

Hai đá quý là tốt, ví dụ: đá quý json có hai biến thể: ['json'] (https://rubygems.org/gems/json) (với phần mở rộng C) và [' json_pure'] (https://rubygems.org/gems/json_pure) (Ruby thuần khiết) – Stefan

+0

@Stefan: Trong cuộc trò chuyện tôi đã liên kết, tác giả/người bảo trì 'json' và' json_pure' dường như sẽ thích nó hơn. Cũng như công việc phụ xuất bản hai biến thể của đá quý, các dự án phụ thuộc có thể là phụ thuộc cho cái gì đó khác, cuối cùng phải sử dụng mẫu số thấp nhất hoặc cũng phải cung cấp hai biến thể để quản lý các phụ thuộc mà chúng không mã hóa . Tôi sẽ không gọi đó là "tốt", nhưng nếu nó là tốt nhất có thể, thì đó là tất cả tôi có thể làm quá –

+0

@Stefan: Chắc chắn ý định thiết kế của Neil thể hiện trong OP là đúng. Câu hỏi của anh ấy rất tuyệt vời và rất quan trọng, tôi quan tâm đến câu trả lời cho bản thân, xin vui lòng upvote nó. –

Trả lời

1

này được kết quả tốt nhất của tôi cố gắng trả lời câu hỏi của riêng tôi cho đến nay. Nó xuất hiện để làm việc cho JRuby (thử nghiệm trong Travis và cài đặt địa phương của tôi theo RVM), đó là mục tiêu chính của tôi. Tuy nhiên, tôi sẽ rất quan tâm đến việc xác nhận của nó làm việc trong các môi trường khác, và đối với bất kỳ đầu vào như thế nào để làm cho nó chung chung hơn và/hoặc mạnh mẽ:


Mã cài đặt ngọc hy vọng một Makefile như đầu ra từ extconf.rb , nhưng không có ý kiến ​​về những gì cần chứa.Do đó, extconf.rb có thể quyết định tạo không làm gìMakefile, thay vì gọi create_makefile từ mkmf. Trên thực tế rằng có thể trông như thế này:

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 

Như đã đề cập trong câu hỏi, câu trả lời này cũng đòi hỏi sự logic sau đây để tải mã dự phòng Ruby trong chính thư viện:

lib/foo.rb (trích đoạn)

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 

Theo sau tuyến đường này cũng yêu cầu một số thao tác cào bừa, do đó nhiệm vụ cào mặc định không cố gắng biên dịch trên Hồng ngọc mà chúng tôi đang thử nghiệm mà không có khả năng biên dịch các tiện ích:

Rakefile (trích đoạn)

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 

Lưu ý phần Rakefile không phải hoàn toàn chung chung, nó chỉ có để trang trải các môi trường được biết đến chúng tôi muốn ở địa phương xây dựng và thử nghiệm đá quý trên (ví dụ tất cả các mục tiêu Travis).

Tôi đã nhận thấy một sự khó chịu. Đó là theo mặc định, bạn sẽ thấy thông điệp của Ruby Gems Building native extensions. This could take a while... và không có dấu hiệu nào cho thấy việc biên dịch mở rộng bị bỏ qua. Tuy nhiên, nếu bạn gọi trình cài đặt với gem install foo --verbose bạn sẽ thấy các thông báo được thêm vào extconf.rb, vì vậy nó không quá tệ.

1

Đây là suy nghĩ, dựa trên thông tin từ http://guides.rubygems.org/c-extensions/http://yorickpeterse.com/articles/hacking-extconf-rb/.

Có vẻ như bạn có thể đặt logic trong extconf.rb. Ví dụ, truy vấn liên tục RUBY_DESCRIPTION và xác định xem bạn đang ở trong một Ruby có hỗ trợ phần mở rộng tự nhiên:

$ 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]" 

Vì vậy, bạn có thể thử một cái gì đó như quấn mã trong extconf.rb trong một điều kiện (trong extconf.rb):

unless RUBY_DESCRIPTION =~ /jruby/ do 

    require 'mkmf' 

    # stuff  
    create_makefile('my_extension/my_extension') 

end 

Rõ ràng, bạn sẽ muốn lôgic phức tạp hơn, lấy các thông số thông qua ngày "gem install", vv

+0

Mã đó trong 'extconf.rb' không hoạt động. Nhưng mã tương tự trong gemspec xung quanh sự phụ thuộc vào việc cào-cài đặt và tuyên bố phần mở rộng xuất hiện để làm việc theo yêu cầu. Tôi cần phải chơi với nó nhiều hơn một chút, nhưng nó hứa hẹn –

+0

Chỉnh sửa gemspec cho phép tôi kiểm tra đá quý trên JRuby một lần nữa, và tôi đã có tất cả các nhiệm vụ cào của tôi làm việc trong tất cả các thử nghiệm hồng ngọc, nhưng cuối cùng nó đã không làm việc. Vấn đề là '.gemspec' được xử lý quá sớm, vì vậy chỉ hoạt động khi xây dựng viên ngọc được thực hiện trong cùng một đích Ruby. Tuy nhiên 'extconf.rb' được xử lý quá muộn, nếu gem chứa' extconf.rb' thì hệ thống đích có thể từ chối nó trước khi bất kỳ logic nào trong nó chạy. –

+0

Cảm ơn các con trỏ, chúng đã dẫn đến những gì tôi nghĩ là một câu trả lời khả thi. Tuy nhiên, có nhiều hơn nó để phát hiện các mục tiêu mà không/không biên dịch, do đó, đã thêm phát hiện của tôi như là một câu trả lời mới. –

Các vấn đề liên quan