diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index 0a19016..ef66d30 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -70,6 +70,7 @@ class Gem::CommandManager register_command :unpack register_command :update register_command :which + register_command :nix end ## diff --git a/lib/rubygems/commands/nix_command.rb b/lib/rubygems/commands/nix_command.rb new file mode 100644 index 0000000..1d4ec58 --- /dev/null +++ b/lib/rubygems/commands/nix_command.rb @@ -0,0 +1,205 @@ +require 'net/http' +require 'rubygems/command' +require 'rubygems/doc_manager' +require 'rubygems/install_update_options' +require 'rubygems/dependency_installer' +require 'rubygems/local_remote_options' +require 'rubygems/validator' +require 'rubygems/exceptions' +require 'rubygems/version_option' +require 'rubygems/version' +require 'open3' + + +def nixname(gem) + s = gem.kind_of? String ? gem : gem.full_name + s.gsub(/[.-]/,'_') +end + +## +# tool creating nix expression to install gems (from ruby forge etc) +# +# this is work in progress + +class Gem::Commands::NixCommand < Gem::Command + + include Gem::VersionOption + include Gem::LocalRemoteOptions + include Gem::InstallUpdateOptions + + def initialize + defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({ + }) + super 'nix', 'create a nix file containing expressions of the gems', defaults + end + + def description # :nodoc: + <<-EOF + create a nix file containing expressions of the gems + There are many gems. So it's best to only specify some target gems and + take them into acocunt with their deps + TODO more details + EOF + end + + def usage # :nodoc: + "#{program_name} GEMNAME [GEMNAME ...] [options] -- --build-flags" + end + + def arguments # :nodoc: + "GEMNAME name of gem to be added to the expressions file" + end + + def defaults_str # :nodoc: + # what to put in here ? TODO (probably nothing is ok) + "" + end + + def execute + + begin + @prerelease = false; + + args = options[:args]; + + @gems_with_deps = {} + @seen = {} + + # args to dep informations + args.map { |arg| + if arg =~ /(.+)-?(.*)?/ then + gem_name = $1 + version = $2.empty? ? Gem::Requirement.default : Gem::Version.new($2) + else + raise Gem::CommandLineError, "could'nt parse arg. expected: name or name-version" + end + + print "adding gem_name\n" + + adddep(Gem::Dependency.new gem_name, version) + } + + print " total: #{@gems_with_deps.length}\n" + + + out = " + # WARNING: automatically generated CODE + # This section has been generated by gem nix #{args.join(" ")} + # the gem nix command has been added by a nix patch to ruby gems + " + # define aliases + aliases = {} + @gems_with_deps.each{ |key, (spec, src, deps)| + aliases[spec.name] = spec \ + if aliases[spec.name].nil? || aliases[spec.name].version < spec.version + + # get true mirror url reading redirect contents + h = Net::HTTP.new('gems.rubyforge.org', 80) + resp, data = h.get("/gems/#{spec.full_name}.gem", nil) + src_url = resp['location'] + print "redirection: http://gems.rubyforge.org/gems/#{spec.full_name}.gem -> #{src_url}\n" + + out = " + #{out} + #{nixname spec} = rubyDerivation { + name = \"#{spec.full_name}\"; # full_name + nameNoVersion = \"#{spec.name}\"; + propagatedBuildInputs = [ #{deps.map {|n| n.nil? ? "" : (nixname n) }.join(" ")} ]; + src = fetchurl { + url = \"#{src_url}\"; + sha256 = \"#{nixhashof src_url}\"; + }; + meta = { + homepage = \"#{spec.homepage}\"; + license = [#{spec.licenses.map{|l| "\"#{l}\""}.join(" ") }]; # one of ? + description = \"#{ spec.description[0..120] }\"; # cut to 120 chars + longDescription = \"#{ spec.description }\"; + }; + };\n" + } + + out = "#{out}\n# aliases\n" + aliases.each { |key, spec | + out = "#{out}#{nixname key}=#{nixname spec};\n" + } + + print out + exit_code = 0 + + rescue => e + puts e.inspect + puts e.backtrace + end + + + end + + # helper funtions ================ + + def adddep(dep) + gem = find_gem_with_source(dep) + raise Gem::CommandLineError, "couldn't find #{dep}" if gem.nil? + full_name = gem[0].full_name + + return if @seen[full_name] + @seen[full_name] = true # there maybe circular dependencies. thus mark this gem seen as early as possible + + # distinguish runtime / buildtime deps? (TODO) + deps = gem[0].dependencies + + print " total deps of #{full_name}: #{deps.length}\n" + + dep_specs = [] + # recurse while collecting deps + deps.each {|dep_var| dep_specs.push(adddep(dep_var)) } + + + @gems_with_deps[full_name] = [ + gem[0], # spec + gem[1], # src + dep_specs # deps + ] + gem[0] # only return spec, no source for dep list + end + + + # copied from dependency_installer, stripped + def find_gem_with_source(dep) + gems_and_sources = [] + + # no local + + requirements = dep.version_requirements.requirements.map do |req, ver| + req + end + + all = true + found = Gem::SpecFetcher.fetcher.fetch dep, all, true, @prerelease + found.reverse[0] + end + + + def nixhashof(src) + cashfile="#{ENV['HOME']}/.nix-ruby-gems-cache" + cash = {} + if FileTest.exists?(cashfile) + File.open(cashfile,'r') do |f| Marshal.load(f) end + end + + if cash[src].nil? then + tmp="/tmp/ruby-gems-nix-tmp-file" + raise Gem::DependencyError("could'nt nix-prefetch #{src}") \ + if (not system("nix-prefetch-url #{src.gsub(/([:= `$;])/,'\\\\\1')} > #{tmp} 2>/dev/null")) || $? != 0 + file = File.new(tmp) + hash = file.readlines().first().split("\n")[0] # remove trailing \n + file.close() + File.delete(tmp) + cash[src] = hash + + File.open(cashfile, "w+") do |f| Marshal.dump(cash, f) end + end + + return cash[src] + end + +end