Skip to content

Commit 08d6d54

Browse files
committed
Make it loose coupling between RubyGems and RDoc
Problems 1. If there are braking changes in RDoc, RubyGems is also broken. 2. When we maintain RDoc, we have to change RubyGems. The reason why they are happened is that RubyGems creates documents about a gem with installing it. Note that RubyGems uses functions of RDoc to create documents. Specifically, - Creating documents is executed by `rubygems/lib/rubygems/rdoc.rb`. - `::RDoc::RubygemsHook` which is defined by RDoc is called by the file. Solution RubyGems has the plugin system. If a gem includes `rubygems_plugin.rb`, RubyGems loads it. RubyGems executes a process defined in it while installing gems, uninstalling gems or other events. We can use the system to solve the problems. The root cause is RubyGems directly references the class of RDoc. We can remove the root cause by making RDoc RubyGems plugin. Alternatively `rubygems_plugin.rb` creates documents about gems. FAQ Q1. Do we need to change codes of RubyGems? A. No, we don't. If we change codes of RubyGems, we can't keep a compatibility. Example: If we delete codes that uses `RDoc::RubygemsHook` in `rubygems/lib/rubygems/rdoc.rb`, documentations are not created with old RDoc. Q2. When can we delete `rubygems/lib/rubygems/rdoc.rb`? A. We can delete it when all users use RDoc including `rubygems_plugin`. Next ruby version is 3.4. If it includes the RDoc including `rubygems_plugin`, we can delete `rubygems/lib/rubygems/rdoc.rb` after ruby 3.3 is EOL. Q3. Is it a breaking change that Rubygems creates documents with rubygems_plugin not RDoc::RubygemsHook? A. If we simply implement this approach, we move the implementation from `rdoc/lib/rdoc/rubygems_hook.rb` to `rubygems_plugin.rb`. This way can be breaking change. It seems to be fine that we just need to delete `rdoc/rubygems_hook.rb` but it doesn't work. It generates multiple documents. `rubygems/lib/rubygems/rdoc.rb` has the following code. ``` begin require "rdoc/rubygems_hook" # ... rescue LoadError end ``` This code ignores RDoc related processes when `rdoc/rubygems_hook` can't be required. But, this 'require' is not failed. This is because Ruby installs Rdoc as a default gem. So, Rdoc installed as a default gem generates documents and one installed as a normal gem does it too. If you think that this behavior is accectable, we can just delete `rdoc/rubygems_hook.rb`. What do you think about this approach? In this change, we take another approach to solve the problem that creates multiple documents. The reason why the approach that just deletes `rdoc/rubygems.rb` creates multiple documents is `Gem.done_installing(&Gem::RDoc.method(:generation_hook))` in `rubygems/rdoc.rb`. `rubygems/rdoc.rb` が Gem.done_installing しているところで ドキュメント生成がデフォルト gem によって行われているので、既存の Rubygems が require している RDoc::RubygemsHook は何も処理を実行しないようにしている。 ↑ 次回: - 動作確認について、手順、結果 Note When RDoc and other gem were installed, RubyGems plugin of RDoc wasn't executed. For example ``` gem install rdoc pkg-config ``` Expected Installing RDoc creates a document of pkg-config. Actual Installed RDoc creates a document of pkg-config. See also. ruby/rubygems#6673 | RDoc / rubyGems | New | Old | | changed | A | A | | current | A | A | Because RDoc is default gem, インストール済みの RDoc でドキュメント生成される。 ただし、この RDoc のバージョン以降を一度インストールすれば、 以降は Plugin によってドキュメント生成がされる。 旧バージョンとの違いは、 Plugin がドキュメント生成するかどうかの違いで、 アウトプットには違いがない。 ======= テスト結果の説明のために、 rdoc 未インストールの状態で、gem install rdoc pkg-config を実行してみる。 このパターンでは実行結果が Expect になるはず。 => Ruby 3.x 以降で RDoc が default gem から bundled gem になる提案がある。 将来の話、というテイでテストしておくのもあり? => 実際にテストしてみたところ、 installing の rdoc で plugin を利用して document 生成がされた。 テスト方法 default gem がない状態を再現する方法 1. Move to a directory includes default gems. $ cd $(ruby -e 'print RbConfig::CONFIG["rubylibdir"]') 2. Rename 'rdoc' and 'rdoc.rb'. $ mv rdoc{,.bk} $ mv rdoc.rb{,.bk} 3. Move to a directory includes specifications of default gems. $ cd $(gem environment gemdir)/specifications/default 4. Rename 'rdoc-<versions>.gemspec'. $ mv $(echo rdoc-*.gemspec){,.bk}
1 parent 9c14229 commit 08d6d54

File tree

4 files changed

+289
-247
lines changed

4 files changed

+289
-247
lines changed

lib/rdoc/rubygems_hook.rb

Lines changed: 12 additions & 243 deletions
Original file line numberDiff line numberDiff line change
@@ -1,248 +1,17 @@
1-
# frozen_string_literal: true
2-
require 'rubygems/user_interaction'
3-
require 'fileutils'
4-
require_relative '../rdoc'
5-
6-
##
7-
# Gem::RDoc provides methods to generate RDoc and ri data for installed gems
8-
# upon gem installation.
91
#
10-
# This file is automatically required by RubyGems 1.9 and newer.
11-
12-
class RDoc::RubygemsHook
13-
14-
include Gem::UserInteraction
15-
extend Gem::UserInteraction
16-
17-
@rdoc_version = nil
18-
@specs = []
19-
20-
##
21-
# Force installation of documentation?
22-
23-
attr_accessor :force
24-
25-
##
26-
# Generate rdoc?
27-
28-
attr_accessor :generate_rdoc
29-
30-
##
31-
# Generate ri data?
32-
33-
attr_accessor :generate_ri
34-
35-
class << self
36-
37-
##
38-
# Loaded version of RDoc. Set by ::load_rdoc
39-
40-
attr_reader :rdoc_version
41-
42-
end
43-
44-
##
45-
# Post installs hook that generates documentation for each specification in
46-
# +specs+
47-
48-
def self.generation_hook installer, specs
49-
start = Time.now
50-
types = installer.document
51-
52-
generate_rdoc = types.include? 'rdoc'
53-
generate_ri = types.include? 'ri'
54-
55-
specs.each do |spec|
56-
new(spec, generate_rdoc, generate_ri).generate
57-
end
58-
59-
return unless generate_rdoc or generate_ri
60-
61-
duration = (Time.now - start).to_i
62-
names = specs.map(&:name).join ', '
63-
64-
say "Done installing documentation for #{names} after #{duration} seconds"
65-
end
66-
67-
##
68-
# Loads the RDoc generator
69-
70-
def self.load_rdoc
71-
return if @rdoc_version
72-
73-
require_relative 'rdoc'
74-
75-
@rdoc_version = Gem::Version.new ::RDoc::VERSION
76-
end
77-
78-
##
79-
# Creates a new documentation generator for +spec+. RDoc and ri data
80-
# generation can be enabled or disabled through +generate_rdoc+ and
81-
# +generate_ri+ respectively.
82-
#
83-
# Only +generate_ri+ is enabled by default.
84-
85-
def initialize spec, generate_rdoc = false, generate_ri = true
86-
@doc_dir = spec.doc_dir
87-
@force = false
88-
@rdoc = nil
89-
@spec = spec
90-
91-
@generate_rdoc = generate_rdoc
92-
@generate_ri = generate_ri
93-
94-
@rdoc_dir = spec.doc_dir 'rdoc'
95-
@ri_dir = spec.doc_dir 'ri'
96-
end
97-
98-
##
99-
# Removes legacy rdoc arguments from +args+
100-
#--
101-
# TODO move to RDoc::Options
102-
103-
def delete_legacy_args args
104-
args.delete '--inline-source'
105-
args.delete '--promiscuous'
106-
args.delete '-p'
107-
args.delete '--one-file'
108-
end
109-
110-
##
111-
# Generates documentation using the named +generator+ ("darkfish" or "ri")
112-
# and following the given +options+.
113-
#
114-
# Documentation will be generated into +destination+
115-
116-
def document generator, options, destination
117-
generator_name = generator
118-
119-
options = options.dup
120-
options.exclude ||= [] # TODO maybe move to RDoc::Options#finish
121-
options.setup_generator generator
122-
options.op_dir = destination
123-
Dir.chdir @spec.full_gem_path do
124-
options.finish
125-
end
126-
127-
generator = options.generator.new @rdoc.store, options
128-
129-
@rdoc.options = options
130-
@rdoc.generator = generator
131-
132-
say "Installing #{generator_name} documentation for #{@spec.full_name}"
133-
134-
FileUtils.mkdir_p options.op_dir
135-
136-
Dir.chdir options.op_dir do
137-
begin
138-
@rdoc.class.current = @rdoc
139-
@rdoc.generator.generate
140-
ensure
141-
@rdoc.class.current = nil
142-
end
143-
end
144-
end
145-
146-
##
147-
# Generates RDoc and ri data
148-
149-
def generate
150-
return if @spec.default_gem?
151-
return unless @generate_ri or @generate_rdoc
152-
153-
setup
154-
155-
options = nil
156-
157-
args = @spec.rdoc_options
158-
args.concat @spec.source_paths
159-
args.concat @spec.extra_rdoc_files
160-
161-
case config_args = Gem.configuration[:rdoc]
162-
when String then
163-
args = args.concat config_args.split(' ')
164-
when Array then
165-
args = args.concat config_args
166-
end
167-
168-
delete_legacy_args args
169-
170-
Dir.chdir @spec.full_gem_path do
171-
options = ::RDoc::Options.new
172-
options.default_title = "#{@spec.full_name} Documentation"
173-
options.parse args
174-
end
175-
176-
options.quiet = !Gem.configuration.really_verbose
177-
178-
@rdoc = new_rdoc
179-
@rdoc.options = options
180-
181-
store = RDoc::Store.new
182-
store.encoding = options.encoding
183-
store.dry_run = options.dry_run
184-
store.main = options.main_page
185-
store.title = options.title
186-
187-
@rdoc.store = store
188-
189-
say "Parsing documentation for #{@spec.full_name}"
190-
191-
Dir.chdir @spec.full_gem_path do
192-
@rdoc.parse_files options.files
193-
end
194-
195-
document 'ri', options, @ri_dir if
196-
@generate_ri and (@force or not File.exist? @ri_dir)
197-
198-
document 'darkfish', options, @rdoc_dir if
199-
@generate_rdoc and (@force or not File.exist? @rdoc_dir)
200-
end
201-
202-
##
203-
# #new_rdoc creates a new RDoc instance. This method is provided only to
204-
# make testing easier.
205-
206-
def new_rdoc # :nodoc:
207-
::RDoc::RDoc.new
208-
end
209-
210-
##
211-
# Is rdoc documentation installed?
212-
213-
def rdoc_installed?
214-
File.exist? @rdoc_dir
215-
end
216-
217-
##
218-
# Removes generated RDoc and ri data
219-
220-
def remove
221-
base_dir = @spec.base_dir
222-
223-
raise Gem::FilePermissionError, base_dir unless File.writable? base_dir
224-
225-
FileUtils.rm_rf @rdoc_dir
226-
FileUtils.rm_rf @ri_dir
227-
end
228-
229-
##
230-
# Is ri data installed?
231-
232-
def ri_installed?
233-
File.exist? @ri_dir
234-
end
235-
236-
##
237-
# Prepares the spec for documentation generation
238-
239-
def setup
240-
self.class.load_rdoc
2+
# This class is referenced by RubyGems to create documents.
3+
# Now, methods are moved to rubygems_plugin.rb.
4+
#
5+
# When old version RDoc is not used,
6+
# this class is not used from RubyGems too.
7+
# Then, remove this class.
8+
#
9+
module RDoc
10+
class RubygemsHook
11+
def initialize(spec); end
24112

242-
raise Gem::FilePermissionError, @doc_dir if
243-
File.exist?(@doc_dir) and not File.writable?(@doc_dir)
13+
def remove; end
24414

245-
FileUtils.mkdir_p @doc_dir unless File.exist? @doc_dir
15+
def self.generation_hook installer, specs; end
24616
end
247-
24817
end

0 commit comments

Comments
 (0)