Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Support normal gem require without explicit 'gem "name"' for default gem #377

Merged
merged 12 commits into from

6 participants

@kou
Collaborator

Let "test-unit" is a default gem.

Before:

require "test/unit" # -> test/unit.rb of the standard library is loaded

gem "test-unit"
require "test/unit" # -> test/unit.rb test-unit gem is loaded

After:

require "test/unit" # -> test/unit.rb of test-unit gem is loaded

Default gem related works are not completed. There are following
not implemented features:

  • Register the default gems
  • Support "gem contents" for RDoc
  • Block "gem uninstall default-gem"

This change shows what I want to do for implementing default gem. If
you accept this change, I will do the next works.

Note that I want to move Kerenel#require codes in
lib/rubygems/custom_require.rb to Gem::CustomRequire#run in the
future.

@kou
Collaborator

@drbrain, if you are busy to check this change, I'll continue this work a week later. If my work is bad, we can revert it later.

lib/rubygems.rb
((13 lines not shown))
+ ##
+ # Find a Gem::Specification of default gem from +path+
+
+ def find_unresolved_default_spec(path)
+ Gem.suffixes.each do |suffix|
+ spec = @path_to_default_spec_map["#{path}#{suffix}"]
+ return spec if spec
+ end
+ nil
+ end
+
+ ##
+ # Remove needless Gem::Specification of default gem from
+ # unresolved default gem list
+
+ def remove_unresoleved_default_spec(spec)
@drbrain Owner
drbrain added a note

There is a typo, an extra "e" in "unresoleved", remove_unresolved_default_spec is correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/rubygems/custom_require.rb
@@ -4,6 +4,22 @@
# See LICENSE.txt for permissions.
#++
+module Gem
+ class CustomRequire
@drbrain Owner
drbrain added a note

Can you flatten this to class Gem::CustomRequire, most other RubyGems classes are created this way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/rubygems/custom_require.rb
@@ -4,6 +4,22 @@
# See LICENSE.txt for permissions.
#++
+module Gem
+ class CustomRequire
+ def initialize(path)
+ @path = path
+ end
+
+ def run
+ spec = Gem.find_unresolved_default_spec(@path)
+ if spec
+ Gem.remove_unresoleved_default_spec(spec)
@drbrain Owner
drbrain added a note

Typo from lib/rubygems.rb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@drbrain
Owner

I think this is a good start, please commit it.

Please proceed with moving the Kernel#require codes to Gem::CustomRequire

@kou
Collaborator
kou commented

Thanks for your review!
I'll apply your comments and continue my work.

@evanphx
Owner

Can you remove the Gem::CustomRequire class please? It's not necessary.

Also, I'd prefer to merge this in when the code that actually calls register_default_spec is also committed, so that I can get a good feeling for how it's going to be used.

@drbrain
Owner

@evanphx I think having a thin Kernel#require and having the logic of require in CustomRequire is acceptable.

@kou
Collaborator
kou commented

@evanphx What about the following steps?

  1. Remove Gem::CustomRequire
  2. Add registering default gems feature
  3. Merge this changes
  4. Try Gem::CustomRequire again as another pull request

I want to put default gem feature to Ruby 2.0. So I will drop Gem::CustomRequire feature if it blocks default gem feature.

@kou
Collaborator

I removed Gem::CustomRequire and implemented registering the default gems feature.

The changes assume that the default gems are placed at #{GEM_HOME}/specifications/default/*.gemspec. Is it right place? Should we use defaults instead of default?

If the changes are OK, I'll merge them and work on master branch.

@kou
Collaborator

@evanphx ping. Could you review my new changes?

@drbrain
Owner

I think this is a good location. I prefer default.

What happens when the user changes ENV["GEM_HOME"]?

Will the default specifications still be discovered?

@voxik

May I ask why have you chosen such hard way? Rubygems already supports multiple GEM_PATHs, so why don't you place the default gems into different GEM_PATH? Since RubyGems can install and uninstall gems just from GEM_HOME, they will be protected from uninstallation. For that it is enough to

1) Rewrite Gem.default_path [1] to include lets say ruby/default_gems, with standard directories such as gems, specifications, docs, etc.
2) Move the parts of StdLib into that directory structure and that is it.

Actually Fedora is using this approach already, the only missing piece is blessing from upstream.

[1] https://github.com/rubygems/rubygems/blob/master/lib/rubygems/defaults.rb#L63

@voxik

Just for your curiosity, this [1] is upstream repository of Fedora's package. You can find there the .spec file and all patches applied. The .spec file is the main file, which describes Fedora's compilation process. I.e. in %prep section, there are applied patches, in %build section is executed the build process and finally, in %install section, we do installation, install custom operating_system.rb [2] and moves the part of StdLib in places where they should be if they were gems [3]. Additionally, we need to do some minor tweaks to .gemspec [4] to ensure they correctly reflect the rubygems layout.

[1] http://pkgs.fedoraproject.org/cgit/ruby.git/tree/
[2] http://pkgs.fedoraproject.org/cgit/ruby.git/tree/ruby.spec#n401
[3] http://pkgs.fedoraproject.org/cgit/ruby.git/tree/ruby.spec#n411
[4] http://pkgs.fedoraproject.org/cgit/ruby.git/tree/ruby.spec#n441

@voxik

This ensures that gem command cannot modify the system gems, since they are maintained by RPM/YUM. This also allows to update JSON gem for example, if newer release is available. Moreover, we keep only one version of gem on the system, as per the Fedoras policies.

lib/rubygems/specification.rb
((5 lines not shown))
+ self.dirs.each { |dir|
+ Dir[File.join(dir, gemspec_glob)].each { |path|
+ spec = Gem::Specification.load path.untaint
+ # #load returns nil if the spec is bad, so we just ignore
+ # it at this stage
+ yield(spec) if spec
+ }
+ }
+ end
+
+ def self._each_default(&block) # :nodoc:
+ _each_spec(File.join("default", "*.gemspec"), &block)
+ end
+
+ def self._each_normal(&block) # :nodoc:
+ _each_spec(File.join("*.gemspec"), &block)
@evanphx Owner
evanphx added a note

No need for a File.join here, it's only one argument.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/rubygems/specification.rb
@@ -611,19 +611,35 @@ def test_files= files
attr_accessor :specification_version
+ def self._each_spec(gemspec_glob) # :nodoc:
@evanphx Owner
evanphx added a note

If these methods are meant to be private, let's actually make them private rather than prefixing with an underscore. You can do that by putting them in a class << self block and using private inside it before the methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@evanphx
Owner

@voxik The reason it's not using the normal GEM_PATH mechanism is that it actually injects the default gems "in front of" the standard library. Meaning the default gems are attempted to be activated and used before the standard library. You can see this reflect in the change to our #require.

@drbrain
Owner

Also, changing the structure of the standard library is too radical a change for ruby developers at this time.

@voxik

@evanphx Gems are alway in front of standard library, since their paths are always searched sooner. The only thing is how to activate them. But once you remove the offending files from StdLib into appropriate gems, they gets activated. So no problem with that.

@drbrain How it could be radical? In what sense? I would suggest to move the gems out of stdlib into gems subdirectory of Ruby development tree, but that is all. You see that it works even without that for Fedora. What else is radical about it?

Actually the way I proposing is return to roots of RubyGems. Rake, Psych, RDoc were used to be independent gems (well they used to be even non gems I guess), but later they were crippled to be part of StdLib. Although that decision probably came earlier then the decision to make RubyGems integral part of Ruby. But instead of taking advantage of it and revert the wrong decision, you build on it and now crippling also RubyGems. These are sad days.

@drbrain
Owner

The majority of the ruby committers don't want to move the files. If you want the ruby committers to move the files please take this issue up on http://bugs.ruby-lang.org or the ruby-core mailing list. This issue is not the appropriate place to discuss such issues.

I appreciate your feelings on this matter, but such a change is not going to happen in ruby 2.0, so we're going to move ahead with this solution as implemented by @kou.

@zenspider
Owner
@voxik

@drbrain If [1] is not targeted for 2.0, so why continue this way?

[1] http://bugs.ruby-lang.org/issues/5481

@drbrain
Owner

To my knowledge, "gemify" has not yet meant "change the layout of the ruby repository". I have been unable to convince other ruby committers that this is the best path. Perhaps it will be possible for ruby 3.0, but it is definitely too late for ruby 2.0.

@zzak
Collaborator

@voxik You have taken this way off-course, please keep discussion related to this ticket

@voxik

@zzak sorry, but this is very relevant ... not merging this commit and fix stdlib is very relevant

@kou
Collaborator

@drbrain Thanks for your comment. I will use default.

I didn't think about ENV["GEM_HOME"]. I tried it. If an user changed both ENV["GEM_HOME"] and ENV["GEM_PATH"], the current implementation doesn't work. It is better that we always search #{Gem.default_path}/specifications/default/*.gemspec. I will do it.

@kou
Collaborator

@evanphx Thanks for your review. I've applied your suggestions.

Can I merge this branch into master and work on master? Or should I continue to work on this branch?

@kou
Collaborator

I've implemented for the following features for default gem:

  • Loading normal gems rather than the default gems if a higher version normal gem exists
  • Support "gem contents default-gem"
  • Block "gem uninstall default-gem"

And I rebased on to the current master.

The following works are needed to complete default gem support:

  • Merging this changes into master
  • Putting the latest RubyGems into the Ruby's repository
  • Modifying *.gemspec in the Ruby's repository
  • Testing the latest RubyGems with RDoc

I have a problem the last work. Does someone know what RDoc command failed with empty "gem contents"? If I know it, I can test it.

kou added some commits
@kou kou Support normal gem require without explicit 'gem "name"' for default gem
Let "test-unit" is a default gem.

Before:

    require "test/unit" # -> test/unit.rb of the standard library is loaded

    gem "test-unit"
    require "test/unit" # -> test/unit.rb test-unit gem is loaded

After:

    require "test/unit" # -> test/unit.rb of test-unit gem is loaded

Default gem related works are not completed. There are following
not implemented features:

  * Register the default gems
  * Support "gem contents" for RDoc
  * Block "gem uninstall default-gem"

This change shows what I want to do for implementing default gem. If
you accept this change, I will do the next works.

Note that I want to move Kerenel#require codes in
lib/rubygems/custom_require.rb to Gem::CustomRequire#run in the
future.
0262778
@kou kou Fix a typo
unresoleved ->
unresolved

Pointed out by @drbrain. Thanks!!!
018a90e
@kou kou Don't create new class Gem::CustomRequire
Suggested by Evan Phoenix.

I'll re-propose this change later. I don't work on it with default gem
changes.
e627cda
@kou kou Add missing "lib/" dabde2d
@kou kou Support loading the default gems automatically
The default gems should be placed at
#{GEM_HOME}/specifications/default/*.gemspec.
Is it right place? Should we use 'defaults' instead of 'default'?
66b70bb
@kou kou Remove needless File.join
Suggested by Evan Phoenix. Thanks!!!
d333341
@kou kou Use private instead of _XXX
This change does it against only Specification._each_spec,
_each_normal and _each_default. We should change other methods such as
_all and _resort! but it is not a work for this branch.

Suggested by Evan Phoenix. Thanks!!!
d598fdc
@kou kou Use GEM_HOME independent search path for default gems
Default gems should be placed at
#{Gem.default_dir}/specifications/default/*.gemspec instead of
#{GEM_HOME}/specifications/default/*.gemspec.

If a user set both of ENV["GEM_HOME"] and GEM["GEM_PATH"], default
gems couldn't be detected.

Reported by Eric Hodel. Thanks!!!
60ddd38
@kou kou Reject uninstalling a default gem
Note: Gem::Specification#default_gem? is suitable name? Should it be
private?
b948b81
@kou kou Add missing 'end's
It is merging miss...
abd9937
@kou kou Support "gem contents" for default gem 6834877
@kou
Collaborator

I rebased on to the current master again.

@drbrain
Owner

gem contents io-console should be a good example

@kou
Collaborator

Thanks.
I checked it and understood that it's difficult... I'll think about it...

@kou
Collaborator

Could you tell me the expected output of gem contents io-console?

Here are candidates I thought:

1) It shows installed files:

% gem contents io-console
${PREFIX}/lib/ruby/2.0.0/console/size.rb
${PREFIX}/lib/ruby/2.0.0/x86_64-linux/io/console.so

2) It shows source files:

% gem contents io-console
${PREFIX}/lib/ruby/gems/2.0.0/gems/default/io-console-0.3/console.c
${PREFIX}/lib/ruby/gems/2.0.0/gems/default/io-console-0.3/extconf.h
${PREFIX}/lib/ruby/gems/2.0.0/gems/default/io-console-0.3/extconf.rb
${PREFIX}/lib/ruby/gems/2.0.0/gems/default/io-console-0.3/lib/console/size.rb

If 1) is the expected output, I'll change tool/rbinstall.rb in the Ruby repository. RubyGems side implementation is already done.

If 2) is the expected output, I'll change RubyGems. There is a concern for this candidate. The listed files doesn't exist because Ruby's make install doesn't install source. (It's normal make install behavior.)

If there are no the expected output, could you tell me the expected output?

@drbrain
Owner

1) is sufficient. We can treat built-in gems with C extensions as pre-compiled C extensions.

@kou
Collaborator

Thanks for your answer.
I'll apply the following patch to the Ruby repository when this pull request is merged and the changes are included in the Ruby repository:

Index: tool/rbinstall.rb
===================================================================
--- tool/rbinstall.rb   (revision 37766)
+++ tool/rbinstall.rb   (working copy)
@@ -562,24 +562,95 @@
       src.sub!(/\A#.*/, '')
       eval(src, nil, path)
     end
+
+    def to_ruby
+        <<-GEMSPEC
+Gem::Specification.new do |s|
+  s.name = #{name.dump}
+  s.version = #{version.dump}
+  s.summary = #{summary.dump}
+  s.description = #{description.dump}
+  s.homepage = #{homepage.dump}
+  s.authors = #{authors.inspect}
+  s.email = #{email.inspect}
+  s.files = #{files.inspect}
+end
+        GEMSPEC
+    end
   end
 end

 module RbInstall
   module Specs
+    class FileCollector
+      def initialize(base_dir)
+        @base_dir = base_dir
+      end
+
+      def collect
+        ruby_libraries + built_libraries
+      end
+
+      private
+      def type
+        /\/(ext|lib)?\/.*?\z/ =~ @base_dir
+        $1
+      end
+
+      def ruby_libraries
+        case type
+        when "ext"
+          prefix = "#{$extout}/common/"
+          base = "#{prefix}#{relative_base}"
+        when "lib"
+          base = @base_dir
+          prefix = base.sub(/lib\/.*?\z/, "") + "lib/"
+        end
+
+        Dir.glob("#{base}{.rb,/**/*.rb}").collect do |ruby_source|
+          remove_prefix(prefix, ruby_source)
+        end
+      end
+
+      def built_libraries
+        case type
+        when "ext"
+          prefix = "#{$extout}/#{CONFIG['arch']}/"
+          base = "#{prefix}#{relative_base}"
+          Dir.glob("#{base}{.so,/**/*.so}").collect do |built_library|
+            remove_prefix(prefix, built_library)
+          end
+        when "lib"
+          []
+        end
+      end
+
+      def relative_base
+        /\/#{Regexp.escape(type)}\/(.*?)\z/ =~ @base_dir
+        $1
+      end
+
+      def remove_prefix(prefix, string)
+        string.sub(/\A#{Regexp.escape(prefix)}/, "")
+      end
+    end
+
     class Reader < Struct.new(:src)
       def gemspec
         @gemspec ||= begin
-          Gem::Specification.load(src) || raise("invalid spec in #{src}")
+          spec = Gem::Specification.load(src) || raise("invalid spec in #{src}")
+          file_collector = FileCollector.new(File.dirname(src))
+          spec.files = file_collector.collect
+          spec
         end
       end

       def spec_source
-        File.read src
+        @gemspec.to_ruby
       end
     end

-    class Generator < Struct.new(:name, :src, :execs)
+    class Generator < Struct.new(:name, :base_dir, :src, :execs)
       def gemspec
         @gemspec ||= eval spec_source
       end
@@ -591,6 +662,7 @@
   s.version = #{version.dump}
   s.summary = "This #{name} is bundled with Ruby"
   s.executables = #{execs.inspect}
+  s.files = #{files.inspect}
 end
         GEMSPEC
       end
@@ -602,6 +674,11 @@
         } or return
         version.split(%r"=\s*", 2)[1].strip[/\A([\'\"])(.*?)\1/, 2]
       end
+
+      def files
+        file_collector = FileCollector.new(base_dir)
+        file_collector.collect
+      end
     end
   end
 end
@@ -615,6 +692,9 @@
   prepare "default gems", gem_dir, directories

   spec_dir = File.join(gem_dir, directories.grep(/^spec/)[0])
+  default_spec_dir = "#{spec_dir}/default"
+  makedirs(default_spec_dir)
+
   gems = {}
   File.foreach(File.join(srcdir, "defs/default_gems")) do |line|
     line.chomp!
@@ -624,11 +704,12 @@
     line.scan(/\G\s*([^\[\]\s]+|\[([^\[\]]*)\])/) do
       words << ($2 ? $2.split : $1)
     end
-    name, src, execs = *words
-    next unless name and src
+    name, base_dir, src, execs = *words
+    next unless name and base_dir and src

     src       = File.join(srcdir, src)
-    specgen   = RbInstall::Specs::Generator.new(name, src, execs || [])
+    base_dir  = File.join(srcdir, base_dir)
+    specgen   = RbInstall::Specs::Generator.new(name, base_dir, src, execs || [])
     gems[name] ||= specgen
   end

@@ -639,10 +720,12 @@

   gems.sort.each do |name, specgen|
     gemspec   = specgen.gemspec
+    base_dir  = specgen.src.sub(/\A#{Regexp.escape(srcdir)}\//, "")
     full_name = "#{gemspec.name}-#{gemspec.version}"

     puts "#{" "*30}#{gemspec.name} #{gemspec.version}"
-    open_for_install(File.join(spec_dir, "#{full_name}.gemspec"), $data_mode) do
+    gemspec_path = File.join(default_spec_dir, "#{full_name}.gemspec")
+    open_for_install(gemspec_path, $data_mode) do
       specgen.spec_source
     end

Index: defs/default_gems
===================================================================
--- defs/default_gems   (revision 37766)
+++ defs/default_gems   (working copy)
@@ -1,5 +1,5 @@
-# gem      versioning file         [executable files under bin]
-rake       lib/rake/version.rb     [rake]
-rdoc       lib/rdoc.rb         [rdoc ri]
-minitest   lib/minitest/unit.rb
-json       ext/json/lib/json/version.rb
+# gem      base directory      versioning file         [executable files under bin]
+rake       lib/rake        lib/rake/version.rb     [rake]
+rdoc       lib/rdoc        lib/rdoc.rb         [rdoc ri]
+minitest   lib/minitest        lib/minitest/unit.rb
+json       ext/json        ext/json/lib/json/version.rb

All of my works for default gem feature are done except applying the patch to the Ruby repository. :-)

@evanphx evanphx merged commit 0c54ab8 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 14, 2012
  1. @kou

    Support normal gem require without explicit 'gem "name"' for default gem

    kou authored
    Let "test-unit" is a default gem.
    
    Before:
    
        require "test/unit" # -> test/unit.rb of the standard library is loaded
    
        gem "test-unit"
        require "test/unit" # -> test/unit.rb test-unit gem is loaded
    
    After:
    
        require "test/unit" # -> test/unit.rb of test-unit gem is loaded
    
    Default gem related works are not completed. There are following
    not implemented features:
    
      * Register the default gems
      * Support "gem contents" for RDoc
      * Block "gem uninstall default-gem"
    
    This change shows what I want to do for implementing default gem. If
    you accept this change, I will do the next works.
    
    Note that I want to move Kerenel#require codes in
    lib/rubygems/custom_require.rb to Gem::CustomRequire#run in the
    future.
  2. @kou

    Fix a typo

    kou authored
    unresoleved ->
    unresolved
    
    Pointed out by @drbrain. Thanks!!!
  3. @kou

    Don't create new class Gem::CustomRequire

    kou authored
    Suggested by Evan Phoenix.
    
    I'll re-propose this change later. I don't work on it with default gem
    changes.
  4. @kou

    Add missing "lib/"

    kou authored
  5. @kou

    Support loading the default gems automatically

    kou authored
    The default gems should be placed at
    #{GEM_HOME}/specifications/default/*.gemspec.
    Is it right place? Should we use 'defaults' instead of 'default'?
  6. @kou

    Remove needless File.join

    kou authored
    Suggested by Evan Phoenix. Thanks!!!
  7. @kou

    Use private instead of _XXX

    kou authored
    This change does it against only Specification._each_spec,
    _each_normal and _each_default. We should change other methods such as
    _all and _resort! but it is not a work for this branch.
    
    Suggested by Evan Phoenix. Thanks!!!
  8. @kou

    Use GEM_HOME independent search path for default gems

    kou authored
    Default gems should be placed at
    #{Gem.default_dir}/specifications/default/*.gemspec instead of
    #{GEM_HOME}/specifications/default/*.gemspec.
    
    If a user set both of ENV["GEM_HOME"] and GEM["GEM_PATH"], default
    gems couldn't be detected.
    
    Reported by Eric Hodel. Thanks!!!
  9. @kou

    Reject uninstalling a default gem

    kou authored
    Note: Gem::Specification#default_gem? is suitable name? Should it be
    private?
  10. @kou

    Add missing 'end's

    kou authored
    It is merging miss...
  11. @kou
  12. @kou
This page is out of date. Refresh to see the latest.
View
42 lib/rubygems.rb
@@ -130,6 +130,7 @@ module Gem
@configuration = nil
@loaded_specs = {}
+ @path_to_default_spec_map = {}
@platforms = []
@ruby = nil
@sources = nil
@@ -936,6 +937,43 @@ class << self
attr_reader :loaded_specs
##
+ # Register a Gem::Specification for default gem
+
+ def register_default_spec(spec)
+ spec.files.each do |file|
+ @path_to_default_spec_map[file] = spec
+ end
+ end
+
+ ##
+ # Find a Gem::Specification of default gem from +path+
+
+ def find_unresolved_default_spec(path)
+ Gem.suffixes.each do |suffix|
+ spec = @path_to_default_spec_map["#{path}#{suffix}"]
+ return spec if spec
+ end
+ nil
+ end
+
+ ##
+ # Remove needless Gem::Specification of default gem from
+ # unresolved default gem list
+
+ def remove_unresolved_default_spec(spec)
+ spec.files.each do |file|
+ @path_to_default_spec_map.delete(file)
+ end
+ end
+
+ ##
+ # Clear default gem related varibles. It is for test
+
+ def clear_default_specs
+ @path_to_default_spec_map.clear
+ end
+
+ ##
# The list of hooks to be run after Gem::Installer#install extracts files
# and builds extensions
@@ -1025,6 +1063,10 @@ class << self
end
end
+##
+# Loads the default specs.
+Gem::Specification.load_defaults
+
require 'rubygems/core_ext/kernel_gem'
require 'rubygems/core_ext/kernel_require'
View
40 lib/rubygems/commands/contents_command.rb
@@ -1,3 +1,4 @@
+require 'English'
require 'rubygems/command'
require 'rubygems/version_option'
@@ -80,19 +81,36 @@ def execute
terminate_interaction 1 if gem_names.length == 1
end
- gem_path = spec.full_gem_path
- extra = "/{#{spec.require_paths.join ','}}" if options[:lib_only]
- glob = "#{gem_path}#{extra}/**/*"
- files = Dir[glob]
-
- gem_path = File.join gem_path, '' # add trailing / if missing
-
- files.sort.each do |file|
- next if File.directory? file
+ if spec.default_gem?
+ files = spec.files.map do |file|
+ case file
+ when /\A#{spec.bindir}\//
+ [Gem::ConfigMap[:bindir], $POSTMATCH]
+ when /\.so\z/
+ [Gem::ConfigMap[:archdir], file]
+ else
+ [Gem::ConfigMap[:rubylibdir], file]
+ end
+ end
+ else
+ gem_path = spec.full_gem_path
+ extra = "/{#{spec.require_paths.join ','}}" if options[:lib_only]
+ glob = "#{gem_path}#{extra}/**/*"
+ prefix_re = /#{Regexp.escape(gem_path)}\//
+ files = Dir[glob].map do |file|
+ [gem_path, file.sub(prefix_re, "")]
+ end
+ end
- file = file.sub gem_path, '' unless options[:prefix]
+ files.sort.each do |prefix, basename|
+ absolute_path = File.join(prefix, basename)
+ next if File.directory? absolute_path
- say file
+ if options[:prefix]
+ say absolute_path
+ else
+ say basename
+ end
end
end
end
View
5 lib/rubygems/commands/pristine_command.rb
@@ -78,6 +78,11 @@ def execute
say "Restoring gems to pristine condition..."
specs.each do |spec|
+ if spec.default_gem?
+ say "Skipped #{spec.full_name}, it is a default gem"
+ next
+ end
+
unless spec.extensions.empty? or options[:extensions] then
say "Skipped #{spec.full_name}, it needs to compile an extension"
next
View
1  lib/rubygems/compatibility.rb
@@ -32,6 +32,7 @@ module Gem
RbConfigPriorities = %w[
EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name
ruby_version rubylibprefix sitedir sitelibdir vendordir vendorlibdir
+ rubylibdir
]
unless defined?(ConfigMap)
View
6 lib/rubygems/core_ext/kernel_require.rb
@@ -32,6 +32,12 @@ module Kernel
# that file has already been loaded is preserved.
def require path
+ spec = Gem.find_unresolved_default_spec(path)
+ if spec
+ Gem.remove_unresolved_default_spec(spec)
+ gem(spec.name)
+ end
+
# If there are no unresolved deps, then we can use just try
# normal require handle loading a gem from the rescue below.
View
50 lib/rubygems/specification.rb
@@ -611,19 +611,43 @@ def test_files= files
attr_accessor :specification_version
- def self._all # :nodoc:
- unless defined?(@@all) && @@all then
-
- specs = {}
+ class << self
+ def default_specifications_dir
+ File.join(Gem.default_dir, "specifications", "default")
+ end
- self.dirs.each { |dir|
+ private
+ def each_spec(search_dirs) # :nodoc:
+ search_dirs.each { |dir|
Dir[File.join(dir, "*.gemspec")].each { |path|
spec = Gem::Specification.load path.untaint
# #load returns nil if the spec is bad, so we just ignore
# it at this stage
- specs[spec.full_name] ||= spec if spec
+ yield(spec) if spec
}
}
+ end
+
+ def each_default(&block) # :nodoc:
+ each_spec([default_specifications_dir],
+ &block)
+ end
+
+ def each_normal(&block) # :nodoc:
+ each_spec(dirs, &block)
+ end
+ end
+
+ def self._all # :nodoc:
+ unless defined?(@@all) && @@all then
+
+ specs = {}
+ each_default do |spec|
+ specs[spec.full_name] ||= spec
+ end
+ each_normal do |spec|
+ specs[spec.full_name] ||= spec
+ end
@@all = specs.values
@@ -641,6 +665,15 @@ def self._resort! # :nodoc:
end
##
+ # Loads the default specifications. It should be called only once.
+
+ def self.load_defaults
+ each_default do |spec|
+ Gem.register_default_spec(spec)
+ end
+ end
+
+ ##
# Adds +spec+ to the known specifications, keeping the collection
# properly sorted.
@@ -2481,6 +2514,11 @@ def reset_nil_attributes_to_default
end
end
+ def default_gem?
+ loaded_from &&
+ File.dirname(loaded_from) == self.class.default_specifications_dir
+ end
+
extend Gem::Deprecate
# TODO:
View
36 lib/rubygems/test_case.rb
@@ -149,6 +149,11 @@ def setup
FileUtils.mkdir_p @gemhome
FileUtils.mkdir_p @userhome
+ @default_dir = File.join @tempdir, 'default'
+ @default_spec_dir = File.join @default_dir, "specifications", "default"
+ Gem.instance_variable_set :@default_dir, @default_dir
+ FileUtils.mkdir_p @default_spec_dir
+
# We use Gem::Specification.reset the first time only so that if there
# are unresolved deps that leak into the whole test suite, they're at least
# reported once.
@@ -163,6 +168,7 @@ def setup
Gem::Security.reset
Gem.loaded_specs.clear
+ Gem.clear_default_specs
Gem::Specification.unresolved_deps.clear
Gem.configuration.verbose = true
@@ -252,6 +258,8 @@ def teardown
else
ENV.delete 'HOME'
end
+
+ Gem.instance_variable_set :@default_dir, nil
end
##
@@ -453,6 +461,16 @@ def install_specs(*specs)
end
##
+ # Install the provided default specs
+
+ def install_default_specs(*specs)
+ install_specs(*specs)
+ specs.each do |spec|
+ Gem.register_default_spec(spec)
+ end
+ end
+
+ ##
# Create a new spec (or gem if passed an array of files) and set it
# up properly. Use this instead of util_spec and util_gem.
@@ -496,6 +514,24 @@ def new_spec name, version, deps = nil, *files
spec
end
+ def new_default_spec(name, version, deps = nil, *files)
+ spec = new_spec(name, version, deps)
+ spec.loaded_from = File.join(@default_spec_dir, spec.spec_name)
+ spec.files = files
+
+ lib_dir = File.join(@tempdir, "default_gems", "lib")
+ $LOAD_PATH.unshift(lib_dir)
+ files.each do |file|
+ rb_path = File.join(lib_dir, file)
+ FileUtils.mkdir_p(File.dirname(rb_path))
+ File.open(rb_path, "w") do |rb|
+ rb << "# #{file}"
+ end
+ end
+
+ spec
+ end
+
##
# Creates a spec with +name+, +version+ and +deps+.
View
16 lib/rubygems/uninstaller.rb
@@ -74,14 +74,26 @@ def initialize(gem, options = {})
def uninstall
list = Gem::Specification.find_all_by_name(@gem, @version)
+ default_specs, list = list.partition do |spec|
+ spec.default_gem?
+ end
+
list, other_repo_specs = list.partition do |spec|
@gem_home == spec.base_dir or
(@user_install and spec.base_dir == Gem.user_dir)
end
if list.empty? then
- raise Gem::InstallError, "gem #{@gem.inspect} is not installed" if
- other_repo_specs.empty?
+ if other_repo_specs.empty?
+ if default_specs.empty?
+ raise Gem::InstallError, "gem #{@gem.inspect} is not installed"
+ else
+ message =
+ "gem #{@gem.inspect} cannot be uninstalled " +
+ "because it is a default gem"
+ raise Gem::InstallError, message
+ end
+ end
other_repos = other_repo_specs.map { |spec| spec.base_dir }.uniq
View
23 test/rubygems/test_gem_commands_contents_command.rb
@@ -127,6 +127,29 @@ def test_execute_no_prefix
assert_equal "", @ui.error
end
+ def test_execute_default_gem
+ default_gem_spec = new_default_spec("default", "2.0.0.0",
+ nil, "default/gem.rb")
+ default_gem_spec.executables = ["default_command"]
+ default_gem_spec.files += ["default_gem.so"]
+ install_default_specs(default_gem_spec)
+
+ @cmd.options[:args] = %w[default]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ expected = <<-EOF
+#{Gem::ConfigMap[:bindir]}/default_command
+#{Gem::ConfigMap[:rubylibdir]}/default/gem.rb
+#{Gem::ConfigMap[:archdir]}/default_gem.so
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
def test_handle_options
refute @cmd.options[:lib_only]
assert @cmd.options[:prefix]
View
18 test/rubygems/test_gem_commands_pristine_command.rb
@@ -227,5 +227,23 @@ def test_execute_no_gem
assert_match %r|at least one gem name|, e.message
end
+ def test_execute_default_gem
+ default_gem_spec = new_default_spec("default", "2.0.0.0",
+ nil, "default/gem.rb")
+ install_default_specs(default_gem_spec)
+
+ @cmd.options[:args] = %w[default]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal([
+ "Restoring gems to pristine condition...",
+ "Skipped default-2.0.0.0, it is a default gem",
+ ],
+ @ui.output.split("\n"))
+ assert_empty(@ui.error)
+ end
end
View
19 test/rubygems/test_gem_commands_uninstall_command.rb
@@ -174,5 +174,24 @@ def test_execute_with_force_ignores_dependencies
assert Gem::Specification.find_all_by_name('dep_x').length > 0
assert Gem::Specification.find_all_by_name('x').length == 0
end
+
+ def test_execute_default_gem
+ default_gem_spec = new_default_spec("default", "2.0.0.0",
+ nil, "default/gem.rb")
+ install_default_specs(default_gem_spec)
+
+ ui = Gem::MockGemUi.new
+
+ @cmd.options[:args] = %w[default]
+ @cmd.options[:executables] = true
+
+ use_ui ui do
+ e = assert_raises Gem::InstallError do
+ @cmd.execute
+ end
+ assert_equal "gem \"default\" cannot be uninstalled because it is a default gem",
+ e.message
+ end
+ end
end
View
14 test/rubygems/test_gem_specification.rb
@@ -1861,6 +1861,20 @@ def test_find_inactive_by_path
assert_equal nil, Gem::Specification.find_inactive_by_path('foo')
end
+ def test_load_default_gem
+ Gem::Specification.reset
+ assert_equal [], Gem::Specification.map(&:full_name)
+
+ default_gem_spec = new_default_spec("default", "2.0.0.0",
+ nil, "default/gem.rb")
+ spec_path = File.join(@default_spec_dir, default_gem_spec.spec_name)
+ write_file(spec_path) do |file|
+ file.print(default_gem_spec.to_ruby)
+ end
+ Gem::Specification.reset
+ assert_equal ["default-2.0.0.0"], Gem::Specification.map(&:full_name)
+ end
+
def util_setup_deps
@gem = quick_spec "awesome", "1.0" do |awesome|
awesome.add_runtime_dependency "bonobo", []
View
23 test/rubygems/test_require.rb
@@ -152,6 +152,29 @@ def test_unable_to_find_good_unresolved_version
end
end
+ def test_default_gem_only
+ save_loaded_features do
+ default_gem_spec = new_default_spec("default", "2.0.0.0",
+ nil, "default/gem.rb")
+ install_default_specs(default_gem_spec)
+ assert_require "default/gem"
+ assert_equal %w(default-2.0.0.0), loaded_spec_names
+ end
+ end
+
+ def test_default_gem_and_normal_gem
+ save_loaded_features do
+ default_gem_spec = new_default_spec("default", "2.0.0.0",
+ nil, "default/gem.rb")
+ install_default_specs(default_gem_spec)
+ normal_gem_spec = new_spec("default", "3.0", nil,
+ "lib/default/gem.rb")
+ install_specs(normal_gem_spec)
+ assert_require "default/gem"
+ assert_equal %w(default-3.0), loaded_spec_names
+ end
+ end
+
def loaded_spec_names
Gem.loaded_specs.values.map(&:full_name).sort
end
Something went wrong with that request. Please try again.