dependabot-gradle 0.389.0 → 0.390.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbe1f38539a5dd887e3c8fa64bb88e70766c846ca198c504780303b63464c8ab
4
- data.tar.gz: e5618a45ebe2bd554fb3edaf73b89e5472d5d334919676cac60e154a38b6853f
3
+ metadata.gz: 93f82ad110e67a9345a7a672f828eabb90aac5cfa6349d2f5ec939f18e4a16c5
4
+ data.tar.gz: ea556afb860bfd5bcbb565eb722ebb0523240163d8b99d197972771647dfd080
5
5
  SHA512:
6
- metadata.gz: 0ef872f7affa834445ba93e8f1c47ced0bd9c77165edf43b07eac7de49f7b1d38bf22a80826ed1bf3849b922bd43ca4c1cfd1d72ad4da5c2e38afc7ed41bfdcb
7
- data.tar.gz: 44b02a0f2b977a165d499c33405ddd12fc53424af928fcfa358ecd8c0184b592f60a5ed4455db2465b853b0a987c0c7daa07f247f4c3f15c55905b6425b4341d
6
+ metadata.gz: a447eb2f78a3ab4163126e1fc63bb472d8162a8b1f452de815e1d4fc84caf5610c107af127f9f770b06e06cfda024f9080f41c0b052c30443ed9f604139ff52d
7
+ data.tar.gz: 0551f56c448535c80265c04797a0af1f6f019b8250d41286b99901a2b1740de9bfc4b94eeae966283bbf6c4dbacfa5c0f2bfad7723ae76bf1ac3a7b3703dc145
@@ -33,6 +33,22 @@ module Dependabot
33
33
  subprojects.uniq.map { |name| process_subproject_name(name) }
34
34
  end
35
35
 
36
+ # Returns a map of filesystem directory path => Gradle project name for all declared subprojects.
37
+ # For example: { 'app' => ':app', 'subprojects/chrome-trace' => ':chrome-trace' }
38
+ # This correctly handles custom projectDir mappings in settings files.
39
+ sig { returns(T::Hash[String, String]) }
40
+ def subproject_path_to_name_map
41
+ subprojects = T.let([], T::Array[String])
42
+ process_include_functions(subprojects)
43
+ subprojects.uniq.each_with_object(T.let({}, T::Hash[String, String])) do |name, map|
44
+ path = process_subproject_name(name)
45
+ next unless path
46
+
47
+ # Normalise name to Gradle colon-separated project path (e.g. ':app', ':sub:module')
48
+ map[path] = ":#{name.sub(/^:/, '')}"
49
+ end
50
+ end
51
+
36
52
  private
37
53
 
38
54
  sig { params(subprojects: T::Array[String]).void }
@@ -35,6 +35,9 @@ module Dependabot
35
35
  SUPPORTED_VERSION_CATALOG_FILE_PATH =
36
36
  T.let(%w(/gradle/libs.versions.toml).freeze, T::Array[String])
37
37
 
38
+ PLUGIN_SOURCE_SET_DIRS =
39
+ T.let(%w(src/main/java src/main/kotlin src/main/groovy src/main/resources).freeze, T::Array[String])
40
+
38
41
  sig do
39
42
  override
40
43
  .params(
@@ -88,10 +91,74 @@ module Dependabot
88
91
  files += subproject_buildfiles(root_dir)
89
92
  files += subproject_lockfiles(root_dir)
90
93
  files += dependency_script_plugins(root_dir)
94
+ files += convention_plugin_source_files(files, root_dir) if fetch_plugin_sources_for_lockfiles?
95
+
91
96
  files + included_builds(root_dir)
92
97
  .flat_map { |dir| all_buildfiles_in_build(dir) }
93
98
  end
94
99
 
100
+ sig { returns(T::Boolean) }
101
+ def fetch_plugin_sources_for_lockfiles?
102
+ Dependabot::Experiments.enabled?(:gradle_lockfile_updater)
103
+ end
104
+
105
+ # Only fetch source trees for *nested* project directories (e.g. "build-logic/convention"
106
+ # or "included/convention"), plus the root of a non-top-level build (e.g. "build-logic" or
107
+ # "buildSrc" itself), never the top-level repo root ("app" style modules of the outermost
108
+ # build). This targets the common convention-plugin module pattern - whether declared at an
109
+ # included/buildSrc build's own root or nested within it - without recursively scanning
110
+ # every ordinary top-level application module of the outermost build.
111
+ sig { params(files: T::Array[DependencyFile], root_dir: String).returns(T::Array[DependencyFile]) }
112
+ def convention_plugin_source_files(files, root_dir)
113
+ nested_plugin_project_dirs(files, root_dir).flat_map do |project_dir|
114
+ PLUGIN_SOURCE_SET_DIRS.flat_map do |relative_dir|
115
+ fetch_tree_support_files(clean_join([project_dir, relative_dir]))
116
+ end
117
+ end
118
+ end
119
+
120
+ sig { params(files: T::Array[DependencyFile], root_dir: String).returns(T::Array[String]) }
121
+ def nested_plugin_project_dirs(files, root_dir)
122
+ buildfile_dirs(files).select { |dir| plugin_source_eligible_dir?(dir, root_dir) }
123
+ end
124
+
125
+ # A directory is eligible for convention-plugin source scanning if it's nested below the
126
+ # outermost repo root (contains a "/"), or if it's the own root of a build that is itself
127
+ # nested (i.e. root_dir isn't the outermost "."), such as an included build or buildSrc.
128
+ sig { params(dir: String, root_dir: String).returns(T::Boolean) }
129
+ def plugin_source_eligible_dir?(dir, root_dir)
130
+ return true if root_dir != "." && dir == root_dir
131
+
132
+ dir.include?("/")
133
+ end
134
+
135
+ sig { params(files: T::Array[DependencyFile]).returns(T::Array[String]) }
136
+ def buildfile_dirs(files)
137
+ files.filter_map do |file|
138
+ next unless SUPPORTED_BUILD_FILE_NAMES.include?(File.basename(file.name))
139
+
140
+ clean_join([File.dirname(file.name)])
141
+ end
142
+ end
143
+
144
+ sig { params(dir: String).returns(T::Array[DependencyFile]) }
145
+ def fetch_tree_support_files(dir)
146
+ entries = repo_contents(dir: dir, raise_errors: false)
147
+ return [] if entries.empty?
148
+
149
+ entries.flat_map do |entry|
150
+ entry_path = clean_join([dir, entry.name])
151
+ if entry.type == "dir"
152
+ fetch_tree_support_files(entry_path)
153
+ else
154
+ file = fetch_support_file(entry_path)
155
+ file ? [file] : []
156
+ end
157
+ end
158
+ rescue Dependabot::DependencyFileNotFound
159
+ []
160
+ end
161
+
95
162
  sig { params(root_dir: String).returns(T::Array[String]) }
96
163
  def included_builds(root_dir)
97
164
  builds = []
@@ -7,6 +7,7 @@ require "shellwords"
7
7
  require "sorbet-runtime"
8
8
 
9
9
  require "dependabot/gradle/file_updater"
10
+ require "dependabot/gradle/file_fetcher/settings_file_parser"
10
11
 
11
12
  module Dependabot
12
13
  module Gradle
@@ -15,14 +16,31 @@ module Dependabot
15
16
  extend T::Sig
16
17
 
17
18
  INIT_SCRIPT_TASK_NAME = T.let("dependabotResolveAll", String)
18
-
19
- sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
20
- def initialize(dependency_files:)
19
+ GRADLE_JVMARGS_ATTEMPTS = T.let(
20
+ [
21
+ "-Xmx1536m -Dfile.encoding=UTF-8",
22
+ "-Xmx2048m -Dfile.encoding=UTF-8"
23
+ ].freeze,
24
+ T::Array[String]
25
+ )
26
+ sig do
27
+ params(
28
+ dependency_files: T::Array[Dependabot::DependencyFile],
29
+ repo_contents_path: T.nilable(String)
30
+ ).void
31
+ end
32
+ def initialize(dependency_files:, repo_contents_path: nil)
21
33
  @dependency_files = dependency_files
34
+ @repo_contents_path = repo_contents_path
22
35
  end
23
36
 
24
- sig { params(build_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::DependencyFile]) }
25
- def update_lockfiles(build_file)
37
+ sig do
38
+ params(
39
+ build_file: Dependabot::DependencyFile,
40
+ build_files: T::Array[Dependabot::DependencyFile]
41
+ ).returns(T::Array[Dependabot::DependencyFile])
42
+ end
43
+ def update_lockfiles(build_file, build_files: [build_file])
26
44
  root_dir = determine_root_dir(build_file: build_file)
27
45
  lockfiles = lockfiles_for_root(root_dir)
28
46
 
@@ -36,7 +54,7 @@ module Dependabot
36
54
  cwd = File.join(temp_dir, root_dir == "/" ? "" : root_dir.delete_prefix("/"))
37
55
  FileUtils.mkdir_p(cwd)
38
56
 
39
- write_properties_file(File.join(cwd, "gradle.properties"))
57
+ properties_file_path = File.join(cwd, "gradle.properties")
40
58
 
41
59
  init_script_path = File.join(cwd, "dependabot-locking.init.gradle")
42
60
  write_init_script(init_script_path)
@@ -44,12 +62,17 @@ module Dependabot
44
62
  command_parts = [
45
63
  gradle_executable_for(cwd: cwd, workspace_root: temp_dir.to_s),
46
64
  "--init-script", init_script_path,
47
- INIT_SCRIPT_TASK_NAME,
65
+ *lockfile_tasks_for(build_files),
48
66
  "--write-locks",
49
- "--no-daemon"
67
+ "--no-daemon",
68
+ "--no-configuration-cache"
50
69
  ]
51
70
  command = Shellwords.join(command_parts)
52
- SharedHelpers.run_shell_command(command, cwd: cwd)
71
+ run_lockfile_update_with_retry(
72
+ command: command,
73
+ cwd: cwd,
74
+ properties_file_path: properties_file_path
75
+ )
53
76
 
54
77
  update_lockfiles_content(temp_dir, lockfiles, updated_files)
55
78
  rescue SharedHelpers::HelperSubprocessFailed => e
@@ -171,6 +194,8 @@ module Dependabot
171
194
 
172
195
  sig { params(temp_dir: T.any(Pathname, String)).void }
173
196
  def populate_temp_directory(temp_dir)
197
+ copy_repo_contents_to_temp_dir(temp_dir, @repo_contents_path)
198
+
174
199
  @dependency_files.each do |file|
175
200
  # Handle "/" directory as root - File.join treats "/" as absolute path and ignores prior components
176
201
  relative_dir = file.directory == "/" ? "" : file.directory
@@ -180,8 +205,33 @@ module Dependabot
180
205
  end
181
206
  end
182
207
 
183
- sig { params(file_name: String).void }
184
- def write_properties_file(file_name) # rubocop:disable Metrics/PerceivedComplexity
208
+ sig { params(temp_dir: T.any(Pathname, String), repo_contents_path: T.nilable(String)).void }
209
+ def copy_repo_contents_to_temp_dir(temp_dir, repo_contents_path)
210
+ return if repo_contents_path.nil? || repo_contents_path.strip.empty?
211
+
212
+ source_dir = Pathname.new(repo_contents_path).expand_path
213
+ return unless source_dir.directory?
214
+
215
+ # Use the full checkout when available to ensure Gradle can compile
216
+ # convention plugin implementations from non-manifest source trees.
217
+ Dir.each_child(source_dir.to_s) do |entry|
218
+ entry = T.let(entry, String)
219
+ source_entry = File.join(source_dir.to_s, entry)
220
+ dest_entry = File.join(temp_dir.to_s, entry)
221
+
222
+ if entry == ".git"
223
+ # Some convention plugins shell out to `git` (e.g. for version derivation) and
224
+ # fail without a working repository. Symlink instead of copying to avoid the
225
+ # disk/time cost of duplicating the whole history for every lockfile update.
226
+ File.symlink(source_entry, dest_entry)
227
+ else
228
+ FileUtils.cp_r(source_entry, dest_entry)
229
+ end
230
+ end
231
+ end
232
+
233
+ sig { params(file_name: String, jvmargs: String, base_content: String).void }
234
+ def write_properties_file(file_name, jvmargs:, base_content:) # rubocop:disable Metrics/PerceivedComplexity
185
235
  http_proxy = ENV.fetch("HTTP_PROXY", nil)
186
236
  https_proxy = ENV.fetch("HTTPS_PROXY", nil)
187
237
  http_split = http_proxy&.split(":")
@@ -191,9 +241,20 @@ module Dependabot
191
241
  http_proxy_port = http_split&.fetch(2) || "1080"
192
242
  https_proxy_port = https_split&.fetch(2) || "1080"
193
243
 
194
- existing_content = File.exist?(file_name) ? File.read(file_name) : ""
244
+ existing_jvmargs = base_content[/^org\.gradle\.jvmargs=(.*)$/, 1]
245
+ # Preserve any existing jvmargs (e.g. --add-opens flags) by merging rather than
246
+ # replacing them outright; the raw line is dropped from existing_content below
247
+ # since combined_jvmargs already includes its value.
248
+ has_existing_jvmargs = existing_jvmargs && !existing_jvmargs.strip.empty?
249
+ combined_jvmargs = has_existing_jvmargs ? "#{existing_jvmargs.strip} #{jvmargs}" : jvmargs
250
+ existing_content = base_content.gsub(/^org\.gradle\.jvmargs=.*$\n?/, "")
195
251
 
196
252
  proxy_properties = "
253
+ org.gradle.jvmargs=#{combined_jvmargs}
254
+ org.gradle.workers.max=1
255
+ org.gradle.java.installations.auto-download=false
256
+ org.gradle.java.installations.paths=#{installed_gradle_toolchain_paths}
257
+ kotlin.compiler.execution.strategy=in-process
197
258
  systemProp.http.proxyHost=#{http_proxy_host}
198
259
  systemProp.http.proxyPort=#{http_proxy_port}
199
260
  systemProp.https.proxyHost=#{https_proxy_host}
@@ -203,6 +264,50 @@ systemProp.https.proxyPort=#{https_proxy_port}"
203
264
  File.write(file_name, existing_content + separator + proxy_properties)
204
265
  end
205
266
 
267
+ sig { params(command: String, cwd: String, properties_file_path: String).void }
268
+ def run_lockfile_update_with_retry(command:, cwd:, properties_file_path:)
269
+ base_content = File.exist?(properties_file_path) ? File.read(properties_file_path) : ""
270
+
271
+ catch(:success) do
272
+ GRADLE_JVMARGS_ATTEMPTS.each_with_index do |jvmargs, index|
273
+ write_properties_file(properties_file_path, jvmargs: jvmargs, base_content: base_content)
274
+ begin
275
+ SharedHelpers.run_shell_command(command, cwd: cwd)
276
+ throw :success
277
+ rescue SharedHelpers::HelperSubprocessFailed => e
278
+ # If this is the last attempt, re-raise the error
279
+ raise e if index == GRADLE_JVMARGS_ATTEMPTS.length - 1
280
+
281
+ # Only retry if it's a retryable failure
282
+ raise e unless retryable_daemon_failure?(e)
283
+
284
+ Dependabot.logger.warn(
285
+ "Gradle build failed with jvmargs '#{jvmargs}'. Retrying once with larger heap size..."
286
+ )
287
+ end
288
+ end
289
+ end
290
+ end
291
+
292
+ sig { params(error: SharedHelpers::HelperSubprocessFailed).returns(T::Boolean) }
293
+ def retryable_daemon_failure?(error)
294
+ message = error.message.downcase
295
+ # Retry if daemon disappeared
296
+ if message.include?("daemon disappeared") || message.include?("build daemon disappeared unexpectedly")
297
+ return true
298
+ end
299
+
300
+ # Retry if process was killed (SIGKILL)
301
+ return true if T.cast(error.error_context[:process_termsig], T.nilable(Integer)) == SharedHelpers::SIGKILL
302
+
303
+ false
304
+ end
305
+
306
+ sig { returns(String) }
307
+ def installed_gradle_toolchain_paths
308
+ Dir.glob("/usr/lib/jvm/java-*-openjdk-*").join(",")
309
+ end
310
+
206
311
  sig { params(file_name: String).void }
207
312
  def write_init_script(file_name)
208
313
  # Resolve all resolvable configurations across all loaded projects so
@@ -212,7 +317,13 @@ systemProp.https.proxyPort=#{https_proxy_port}"
212
317
  if (tasks.findByName("#{INIT_SCRIPT_TASK_NAME}") == null) {
213
318
  tasks.register("#{INIT_SCRIPT_TASK_NAME}") {
214
319
  doLast {
215
- configurations.findAll { it.canBeResolved }.each { it.resolve() }
320
+ configurations.findAll {
321
+ it.canBeResolved &&
322
+ it.resolutionStrategy.dependencyLockingEnabled &&
323
+ it.allDependencies.any { dependency ->
324
+ dependency instanceof org.gradle.api.artifacts.ModuleDependency
325
+ }
326
+ }.each { it.incoming.resolutionResult.allDependencies }
216
327
  }
217
328
  }
218
329
  }
@@ -221,6 +332,46 @@ systemProp.https.proxyPort=#{https_proxy_port}"
221
332
  File.write(file_name, script_content)
222
333
  end
223
334
 
335
+ sig { params(build_files: T::Array[Dependabot::DependencyFile]).returns(T::Array[String]) }
336
+ def lockfile_tasks_for(build_files)
337
+ build_files.filter_map { |build_file| dependency_task_for(build_file) }.uniq << INIT_SCRIPT_TASK_NAME
338
+ end
339
+
340
+ sig { params(build_file: Dependabot::DependencyFile).returns(T.nilable(String)) }
341
+ def dependency_task_for(build_file)
342
+ settings_file = find_settings_file(build_file)
343
+ return nil unless settings_file
344
+
345
+ root_dir = determine_root_dir(build_file: build_file)
346
+ file_path = normalized_file_path(build_file)
347
+ relative_path = path_relative_to_root(file_path, root_dir)
348
+
349
+ return nil if relative_path.start_with?("gradle/")
350
+
351
+ dirname = File.dirname(relative_path)
352
+ return nil if dirname == "." || dirname.empty?
353
+
354
+ # Look up the canonical Gradle project name from the settings file to handle repos
355
+ # that use custom projectDir mappings
356
+ # (e.g. project(':chrome-trace').projectDir = file('subprojects/chrome-trace')).
357
+ # Deriving the task from the filesystem path alone would produce a wrong Gradle task path.
358
+ parser = FileFetcher::SettingsFileParser.new(settings_file: settings_file)
359
+ project_name = parser.subproject_path_to_name_map[dirname]
360
+ return nil unless project_name
361
+
362
+ "#{project_name}:dependencies"
363
+ end
364
+
365
+ sig { params(file_path: String, root_dir: String).returns(String) }
366
+ def path_relative_to_root(file_path, root_dir)
367
+ return file_path.sub(%r{^/}, "") if root_dir == "/"
368
+
369
+ root_prefix = "#{root_dir}/"
370
+ return file_path.delete_prefix(root_prefix) if file_path.start_with?(root_prefix)
371
+
372
+ file_path.sub(%r{^/}, "")
373
+ end
374
+
224
375
  sig { params(cwd: String, workspace_root: String).returns(String) }
225
376
  def gradle_executable_for(cwd:, workspace_root:)
226
377
  cwd_path = Pathname.new(cwd).expand_path
@@ -17,7 +17,7 @@ module Dependabot
17
17
  require_relative "file_updater/lockfile_updater"
18
18
  require_relative "file_updater/wrapper_updater"
19
19
 
20
- SUPPORTED_BUILD_FILE_NAMES = %w(build.gradle build.gradle.kts gradle.lockfile).freeze
20
+ SUPPORTED_BUILD_FILE_NAMES = %w(build.gradle build.gradle.kts).freeze
21
21
 
22
22
  sig { override.returns(T::Array[::Dependabot::DependencyFile]) }
23
23
  def updated_dependency_files
@@ -97,7 +97,10 @@ module Dependabot
97
97
  files[T.must(files.index(buildfile))] = update_version_in_buildfile(dependency, buildfile, old_req, new_req)
98
98
  end
99
99
 
100
- buildfiles_processed[buildfile.name] = buildfile
100
+ # Always look up the updated buildfile from files after any modification
101
+ # to ensure buildfiles_processed contains the latest version
102
+ updated_buildfile = files.find { |f| f.name == buildfile.name }
103
+ buildfiles_processed[buildfile.name] = T.must(updated_buildfile)
101
104
  end
102
105
 
103
106
  # runs native updaters (e.g. wrapper, lockfile) on relevant build files updated
@@ -122,17 +125,28 @@ module Dependabot
122
125
  ).void
123
126
  end
124
127
  def update_lockfiles_for_buildfiles(files, buildfiles_processed)
125
- lockfile_roots_processed = T.let(Set.new, T::Set[String])
128
+ # Support files (e.g. fetched convention plugin sources) are excluded from
129
+ # `buildfiles`/`files`, but Gradle still needs them on disk to compile
130
+ # plugins referenced by included/buildSrc builds, so pass them through too.
131
+ support_files = dependency_files.select(&:support_file?)
132
+ lockfile_updater = LockfileUpdater.new(
133
+ dependency_files: files + support_files,
134
+ repo_contents_path: repo_contents_path
135
+ )
136
+ buildfiles_by_root = T.let({}, T::Hash[String, T::Array[Dependabot::DependencyFile]])
126
137
 
127
138
  buildfiles_processed.each_value do |buildfile|
128
- lockfile_updater = LockfileUpdater.new(dependency_files: files)
129
139
  root_dir = lockfile_updater.determine_root_dir(build_file: buildfile)
130
- next if lockfile_roots_processed.include?(root_dir)
140
+ buildfiles_by_root[root_dir] ||= []
141
+ T.must(buildfiles_by_root[root_dir]) << buildfile
142
+ end
131
143
 
132
- lockfile_roots_processed.add(root_dir)
144
+ buildfiles_by_root.each_value do |buildfiles|
145
+ updated_files = lockfile_updater.update_lockfiles(T.must(buildfiles.first), build_files: buildfiles)
133
146
 
134
- updated_files = lockfile_updater.update_lockfiles(buildfile)
135
- replace_updated_files(files, updated_files)
147
+ # Support files are only needed to populate Gradle's working directory;
148
+ # they must never be reintroduced into the buildfiles-only result set.
149
+ replace_updated_files(files, updated_files.reject(&:support_file?))
136
150
  end
137
151
  end
138
152
  sig do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-gradle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.389.0
4
+ version: 0.390.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.389.0
18
+ version: 0.390.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.389.0
25
+ version: 0.390.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: dependabot-maven
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.389.0
32
+ version: 0.390.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 0.389.0
39
+ version: 0.390.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: debug
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -291,7 +291,7 @@ licenses:
291
291
  - MIT
292
292
  metadata:
293
293
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
294
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.389.0
294
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.390.0
295
295
  rdoc_options: []
296
296
  require_paths:
297
297
  - lib