Skip to content

Commit ef92423

Browse files
committed
Make it loose coupling between RubyGems and RDoc
\### Problems There are following problems because of tight coupling between RubyGems and RDoc. 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. This change keeps compatibility of API used from RubyGems. Q2. Is it better to delete existing codes related to RDoc in RubyGems? No, it isn'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. Q3. 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. Q4. Is it a breaking change that Rubygems creates documents with rubygems_plugin not RDoc::RubygemsHook? A. No, it isn't. 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. If `Gem.done_installing(&Gem::RDoc.method(:generation_hook))` in `rubygems/rdoc.rb` doesn't create documents, we can solve the problem. We have some options. * We change `rubygems/rdoc.rb` and then don't execute `Gem.done_installing`. (This is a change for RubyGems.) * We change `rdoc/rubygems_hook.rb` and then make `generation_hook` a no-op method. (This is a change for RDoc.) We choose the latter to avoid changing for RubyGems. \### Test \#### Preparation Install Rdoc which including our changes by executing `rake install`. ❯ rake install We confirmed that Rdoc which including our changes was installed. ❯ gem list | grep rdoc rdoc (6.6.0, default: 6.4.0) \#### Check point We tested to check compatibility. How to chack the compatibility? We tested creating same documents by our RDoc and old RDoc with latest RubyGems. We used following versions to test. ``` ❯ ruby -v ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [arm64-darwin22] ❯ gem list | grep rdoc rdoc (default: 6.4.0) ❯ ruby -I rubygems/lib rubygems/exe/gem --version 3.5.14 ``` Here is a result of test with old RDoc. We can see that the document is created correctlly with `Parsing...` and `Done installing...`. ``` ❯ ruby -I rubygems/lib rubygems/exe/gem install pkg-config Successfully installed pkg-config-1.5.6 Parsing documentation for pkg-config-1.5.6 Done installing documentation for pkg-config after 0 seconds 1 gem installed ``` Here is a result of test with our RDoc. We can see that the document is created correctlly with `Parsing...` and `Done installing...`. ``` ❯ ruby -I rubygems/lib rubygems/exe/gem install pkg-config Successfully installed pkg-config-1.5.6 Parsing documentation for pkg-config-1.5.6 Done installing documentation for pkg-config after 0 seconds 1 gem installed ``` As you can see we got the same results, our RDoc keeps compatibility.
1 parent a576ff8 commit ef92423

File tree

4 files changed

+295
-248
lines changed

4 files changed

+295
-248
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)