PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/Library/Homebrew/cmd/audit.rb

https://bitbucket.org/JoshHagins/homebrew
Ruby | 713 lines | 572 code | 119 blank | 22 comment | 115 complexity | 0e7d528339b8793f4ccb78a979be7e85 MD5 | raw file
  1. require 'formula'
  2. require 'utils'
  3. require 'extend/ENV'
  4. require 'formula_cellar_checks'
  5. module Homebrew
  6. def audit
  7. formula_count = 0
  8. problem_count = 0
  9. ENV.activate_extensions!
  10. ENV.setup_build_environment
  11. ff = if ARGV.named.empty?
  12. Formula
  13. else
  14. ARGV.formulae
  15. end
  16. strict = ARGV.include? "--strict"
  17. ff.each do |f|
  18. fa = FormulaAuditor.new(f, :strict => strict)
  19. fa.audit
  20. unless fa.problems.empty?
  21. formula_count += 1
  22. problem_count += fa.problems.size
  23. puts "#{f.name}:", fa.problems.map { |p| " * #{p}" }, ""
  24. end
  25. end
  26. unless problem_count.zero?
  27. ofail "#{problem_count} problems in #{formula_count} formulae"
  28. end
  29. end
  30. end
  31. class FormulaText
  32. def initialize path
  33. @text = path.open("rb", &:read)
  34. end
  35. def without_patch
  36. @text.split("\n__END__").first
  37. end
  38. def has_DATA?
  39. /^[^#]*\bDATA\b/ =~ @text
  40. end
  41. def has_END?
  42. /^__END__$/ =~ @text
  43. end
  44. def has_trailing_newline?
  45. /\Z\n/ =~ @text
  46. end
  47. end
  48. class FormulaAuditor
  49. include FormulaCellarChecks
  50. attr_reader :formula, :text, :problems
  51. BUILD_TIME_DEPS = %W[
  52. autoconf
  53. automake
  54. boost-build
  55. bsdmake
  56. cmake
  57. imake
  58. intltool
  59. libtool
  60. pkg-config
  61. scons
  62. smake
  63. swig
  64. ]
  65. FILEUTILS_METHODS = FileUtils.singleton_methods(false).join "|"
  66. def initialize(formula, options={})
  67. @formula = formula
  68. @strict = !!options[:strict]
  69. @problems = []
  70. @text = FormulaText.new(formula.path)
  71. @specs = %w{stable devel head}.map { |s| formula.send(s) }.compact
  72. end
  73. def audit_file
  74. unless formula.path.stat.mode == 0100644
  75. problem "Incorrect file permissions: chmod 644 #{formula.path}"
  76. end
  77. if text.has_DATA? and not text.has_END?
  78. problem "'DATA' was found, but no '__END__'"
  79. end
  80. if text.has_END? and not text.has_DATA?
  81. problem "'__END__' was found, but 'DATA' is not used"
  82. end
  83. unless text.has_trailing_newline?
  84. problem "File should end with a newline"
  85. end
  86. end
  87. def audit_class
  88. if @strict
  89. unless formula.test_defined?
  90. problem "A `test do` test block should be added"
  91. end
  92. end
  93. end
  94. @@aliases ||= Formula.aliases
  95. def audit_deps
  96. @specs.each do |spec|
  97. # Check for things we don't like to depend on.
  98. # We allow non-Homebrew installs whenever possible.
  99. spec.deps.each do |dep|
  100. begin
  101. dep_f = dep.to_formula
  102. rescue TapFormulaUnavailableError
  103. # Don't complain about missing cross-tap dependencies
  104. next
  105. rescue FormulaUnavailableError
  106. problem "Can't find dependency #{dep.name.inspect}."
  107. next
  108. end
  109. if @@aliases.include?(dep.name)
  110. problem "Dependency '#{dep.name}' is an alias; use the canonical name '#{dep.to_formula.name}'."
  111. end
  112. dep.options.reject do |opt|
  113. next true if dep_f.option_defined?(opt)
  114. dep_f.requirements.detect do |r|
  115. if r.recommended?
  116. opt.name == "with-#{r.name}"
  117. elsif r.optional?
  118. opt.name == "without-#{r.name}"
  119. end
  120. end
  121. end.each do |opt|
  122. problem "Dependency #{dep} does not define option #{opt.name.inspect}"
  123. end
  124. case dep.name
  125. when *BUILD_TIME_DEPS
  126. next if dep.build? or dep.run?
  127. problem %{#{dep} dependency should be "depends_on '#{dep}' => :build"}
  128. when "git", "ruby", "mercurial"
  129. problem "Don't use #{dep} as a dependency. We allow non-Homebrew #{dep} installations."
  130. when 'gfortran'
  131. problem "Use `depends_on :fortran` instead of `depends_on 'gfortran'`"
  132. when 'open-mpi', 'mpich2'
  133. problem <<-EOS.undent
  134. There are multiple conflicting ways to install MPI. Use an MPIDependency:
  135. depends_on :mpi => [<lang list>]
  136. Where <lang list> is a comma delimited list that can include:
  137. :cc, :cxx, :f77, :f90
  138. EOS
  139. end
  140. end
  141. end
  142. end
  143. def audit_conflicts
  144. formula.conflicts.each do |c|
  145. begin
  146. Formulary.factory(c.name)
  147. rescue FormulaUnavailableError
  148. problem "Can't find conflicting formula #{c.name.inspect}."
  149. end
  150. end
  151. end
  152. def audit_options
  153. formula.options.each do |o|
  154. next unless @strict
  155. if o.name !~ /with(out)?-/ && o.name != "c++11" && o.name != "universal" && o.name != "32-bit"
  156. problem "Options should begin with with/without. Migrate '--#{o.name}' with `deprecated_option`."
  157. end
  158. end
  159. end
  160. def audit_urls
  161. homepage = formula.homepage
  162. unless homepage =~ %r[^https?://]
  163. problem "The homepage should start with http or https (URL is #{homepage})."
  164. end
  165. # Check for http:// GitHub homepage urls, https:// is preferred.
  166. # Note: only check homepages that are repo pages, not *.github.com hosts
  167. if homepage =~ %r[^http://github\.com/]
  168. problem "Use https:// URLs for homepages on GitHub (URL is #{homepage})."
  169. end
  170. # Google Code homepages should end in a slash
  171. if homepage =~ %r[^https?://code\.google\.com/p/[^/]+[^/]$]
  172. problem "Google Code homepage should end with a slash (URL is #{homepage})."
  173. end
  174. urls = @specs.map(&:url)
  175. # Check GNU urls; doesn't apply to mirrors
  176. urls.grep(%r[^(?:https?|ftp)://(?!alpha).+/gnu/]) do |u|
  177. problem "\"ftpmirror.gnu.org\" is preferred for GNU software (url is #{u})."
  178. end
  179. # the rest of the checks apply to mirrors as well
  180. urls.concat(@specs.map(&:mirrors).flatten)
  181. # Check SourceForge urls
  182. urls.each do |p|
  183. # Skip if the URL looks like a SVN repo
  184. next if p =~ %r[/svnroot/]
  185. next if p =~ %r[svn\.sourceforge]
  186. # Is it a sourceforge http(s) URL?
  187. next unless p =~ %r[^https?://.*\b(sourceforge|sf)\.(com|net)]
  188. if p =~ /(\?|&)use_mirror=/
  189. problem "Don't use #{$1}use_mirror in SourceForge urls (url is #{p})."
  190. end
  191. if p =~ /\/download$/
  192. problem "Don't use /download in SourceForge urls (url is #{p})."
  193. end
  194. if p =~ %r[^https?://sourceforge\.]
  195. problem "Use http://downloads.sourceforge.net to get geolocation (url is #{p})."
  196. end
  197. if p =~ %r[^https?://prdownloads\.]
  198. problem "Don't use prdownloads in SourceForge urls (url is #{p}).\n" +
  199. "\tSee: http://librelist.com/browser/homebrew/2011/1/12/prdownloads-is-bad/"
  200. end
  201. if p =~ %r[^http://\w+\.dl\.]
  202. problem "Don't use specific dl mirrors in SourceForge urls (url is #{p})."
  203. end
  204. if p.start_with? "http://downloads"
  205. problem "Use https:// URLs for downloads from SourceForge (url is #{p})."
  206. end
  207. end
  208. # Check for Google Code download urls, https:// is preferred
  209. urls.grep(%r[^http://.*\.googlecode\.com/files.*]) do |u|
  210. problem "Use https:// URLs for downloads from Google Code (url is #{u})."
  211. end
  212. # Check for git:// GitHub repo urls, https:// is preferred.
  213. urls.grep(%r[^git://[^/]*github\.com/]) do |u|
  214. problem "Use https:// URLs for accessing GitHub repositories (url is #{u})."
  215. end
  216. # Check for http:// GitHub repo urls, https:// is preferred.
  217. urls.grep(%r[^http://github\.com/.*\.git$]) do |u|
  218. problem "Use https:// URLs for accessing GitHub repositories (url is #{u})."
  219. end
  220. # Use new-style archive downloads
  221. urls.select { |u| u =~ %r[https://.*github.*/(?:tar|zip)ball/] && u !~ %r[\.git$] }.each do |u|
  222. problem "Use /archive/ URLs for GitHub tarballs (url is #{u})."
  223. end
  224. # Don't use GitHub .zip files
  225. urls.select { |u| u =~ %r[https://.*github.*/(archive|releases)/.*\.zip$] && u !~ %r[releases/download] }.each do |u|
  226. problem "Use GitHub tarballs rather than zipballs (url is #{u})."
  227. end
  228. end
  229. def audit_specs
  230. if head_only?(formula) && formula.tap != "homebrew/homebrew-head-only"
  231. problem "Head-only (no stable download)"
  232. end
  233. %w[Stable Devel HEAD].each do |name|
  234. next unless spec = formula.send(name.downcase)
  235. ra = ResourceAuditor.new(spec).audit
  236. problems.concat ra.problems.map { |problem| "#{name}: #{problem}" }
  237. spec.resources.each_value do |resource|
  238. ra = ResourceAuditor.new(resource).audit
  239. problems.concat ra.problems.map { |problem|
  240. "#{name} resource #{resource.name.inspect}: #{problem}"
  241. }
  242. end
  243. spec.patches.select(&:external?).each { |p| audit_patch(p) }
  244. end
  245. if formula.stable && formula.devel
  246. if formula.devel.version < formula.stable.version
  247. problem "devel version #{formula.devel.version} is older than stable version #{formula.stable.version}"
  248. elsif formula.devel.version == formula.stable.version
  249. problem "stable and devel versions are identical"
  250. end
  251. end
  252. end
  253. def audit_patches
  254. legacy_patches = Patch.normalize_legacy_patches(formula.patches).grep(LegacyPatch)
  255. if legacy_patches.any?
  256. problem "Use the patch DSL instead of defining a 'patches' method"
  257. legacy_patches.each { |p| audit_patch(p) }
  258. end
  259. end
  260. def audit_patch(patch)
  261. case patch.url
  262. when %r[raw\.github\.com], %r[gist\.github\.com/raw], %r[gist\.github\.com/.+/raw],
  263. %r[gist\.githubusercontent\.com/.+/raw]
  264. unless patch.url =~ /[a-fA-F0-9]{40}/
  265. problem "GitHub/Gist patches should specify a revision:\n#{patch.url}"
  266. end
  267. when %r[macports/trunk]
  268. problem "MacPorts patches should specify a revision instead of trunk:\n#{patch.url}"
  269. when %r[^https?://github\.com/.*commit.*\.patch$]
  270. problem "GitHub appends a git version to patches; use .diff instead."
  271. end
  272. end
  273. def audit_text
  274. if text =~ /system\s+['"]scons/
  275. problem "use \"scons *args\" instead of \"system 'scons', *args\""
  276. end
  277. if text =~ /system\s+['"]xcodebuild/
  278. problem %{use "xcodebuild *args" instead of "system 'xcodebuild', *args"}
  279. end
  280. if text =~ /xcodebuild[ (]["'*]/ && text !~ /SYMROOT=/
  281. problem %{xcodebuild should be passed an explicit "SYMROOT"}
  282. end
  283. if text =~ /Formula\.factory\(/
  284. problem "\"Formula.factory(name)\" is deprecated in favor of \"Formula[name]\""
  285. end
  286. end
  287. def audit_line(line, lineno)
  288. if line =~ /<(Formula|AmazonWebServicesFormula|ScriptFileFormula|GithubGistFormula)/
  289. problem "Use a space in class inheritance: class Foo < #{$1}"
  290. end
  291. # Commented-out cmake support from default template
  292. if line =~ /# system "cmake/
  293. problem "Commented cmake call found"
  294. end
  295. # Comments from default template
  296. if line =~ /# PLEASE REMOVE/
  297. problem "Please remove default template comments"
  298. end
  299. if line =~ /# if this fails, try separate make\/make install steps/
  300. problem "Please remove default template comments"
  301. end
  302. if line =~ /# if your formula requires any X11\/XQuartz components/
  303. problem "Please remove default template comments"
  304. end
  305. if line =~ /# if your formula fails when building in parallel/
  306. problem "Please remove default template comments"
  307. end
  308. if line =~ /# Remove unrecognized options if warned by configure/
  309. problem "Please remove default template comments"
  310. end
  311. # FileUtils is included in Formula
  312. # encfs modifies a file with this name, so check for some leading characters
  313. if line =~ /[^'"\/]FileUtils\.(\w+)/
  314. problem "Don't need 'FileUtils.' before #{$1}."
  315. end
  316. # Check for long inreplace block vars
  317. if line =~ /inreplace .* do \|(.{2,})\|/
  318. problem "\"inreplace <filenames> do |s|\" is preferred over \"|#{$1}|\"."
  319. end
  320. # Check for string interpolation of single values.
  321. if line =~ /(system|inreplace|gsub!|change_make_var!).*[ ,]"#\{([\w.]+)\}"/
  322. problem "Don't need to interpolate \"#{$2}\" with #{$1}"
  323. end
  324. # Check for string concatenation; prefer interpolation
  325. if line =~ /(#\{\w+\s*\+\s*['"][^}]+\})/
  326. problem "Try not to concatenate paths in string interpolation:\n #{$1}"
  327. end
  328. # Prefer formula path shortcuts in Pathname+
  329. if line =~ %r{\(\s*(prefix\s*\+\s*(['"])(bin|include|libexec|lib|sbin|share|Frameworks)[/'"])}
  330. problem "\"(#{$1}...#{$2})\" should be \"(#{$3.downcase}+...)\""
  331. end
  332. if line =~ %r[((man)\s*\+\s*(['"])(man[1-8])(['"]))]
  333. problem "\"#{$1}\" should be \"#{$4}\""
  334. end
  335. # Prefer formula path shortcuts in strings
  336. if line =~ %r[(\#\{prefix\}/(bin|include|libexec|lib|sbin|share|Frameworks))]
  337. problem "\"#{$1}\" should be \"\#{#{$2.downcase}}\""
  338. end
  339. if line =~ %r[((\#\{prefix\}/share/man/|\#\{man\}/)(man[1-8]))]
  340. problem "\"#{$1}\" should be \"\#{#{$3}}\""
  341. end
  342. if line =~ %r[((\#\{share\}/(man)))[/'"]]
  343. problem "\"#{$1}\" should be \"\#{#{$3}}\""
  344. end
  345. if line =~ %r[(\#\{prefix\}/share/(info|man))]
  346. problem "\"#{$1}\" should be \"\#{#{$2}}\""
  347. end
  348. # Commented-out depends_on
  349. if line =~ /#\s*depends_on\s+(.+)\s*$/
  350. problem "Commented-out dep #{$1}"
  351. end
  352. # No trailing whitespace, please
  353. if line =~ /[\t ]+$/
  354. problem "#{lineno}: Trailing whitespace was found"
  355. end
  356. if line =~ /if\s+ARGV\.include\?\s+'--(HEAD|devel)'/
  357. problem "Use \"if build.#{$1.downcase}?\" instead"
  358. end
  359. if line =~ /make && make/
  360. problem "Use separate make calls"
  361. end
  362. if line =~ /^[ ]*\t/
  363. problem "Use spaces instead of tabs for indentation"
  364. end
  365. if line =~ /ENV\.x11/
  366. problem "Use \"depends_on :x11\" instead of \"ENV.x11\""
  367. end
  368. # Avoid hard-coding compilers
  369. if line =~ %r{(system|ENV\[.+\]\s?=)\s?['"](/usr/bin/)?(gcc|llvm-gcc|clang)['" ]}
  370. problem "Use \"\#{ENV.cc}\" instead of hard-coding \"#{$3}\""
  371. end
  372. if line =~ %r{(system|ENV\[.+\]\s?=)\s?['"](/usr/bin/)?((g|llvm-g|clang)\+\+)['" ]}
  373. problem "Use \"\#{ENV.cxx}\" instead of hard-coding \"#{$3}\""
  374. end
  375. if line =~ /system\s+['"](env|export)(\s+|['"])/
  376. problem "Use ENV instead of invoking '#{$1}' to modify the environment"
  377. end
  378. if line =~ /version == ['"]HEAD['"]/
  379. problem "Use 'build.head?' instead of inspecting 'version'"
  380. end
  381. if line =~ /build\.include\?[\s\(]+['"]\-\-(.*)['"]/
  382. problem "Reference '#{$1}' without dashes"
  383. end
  384. if line =~ /build\.include\?[\s\(]+['"]with(out)?-(.*)['"]/
  385. problem "Use build.with#{$1}? \"#{$2}\" instead of build.include? 'with#{$1}-#{$2}'"
  386. end
  387. if line =~ /build\.with\?[\s\(]+['"]-?-?with-(.*)['"]/
  388. problem "Don't duplicate 'with': Use `build.with? \"#{$1}\"` to check for \"--with-#{$1}\""
  389. end
  390. if line =~ /build\.without\?[\s\(]+['"]-?-?without-(.*)['"]/
  391. problem "Don't duplicate 'without': Use `build.without? \"#{$1}\"` to check for \"--without-#{$1}\""
  392. end
  393. if line =~ /unless build\.with\?(.*)/
  394. problem "Use if build.without?#{$1} instead of unless build.with?#{$1}"
  395. end
  396. if line =~ /unless build\.without\?(.*)/
  397. problem "Use if build.with?#{$1} instead of unless build.without?#{$1}"
  398. end
  399. if line =~ /(not\s|!)\s*build\.with?\?/
  400. problem "Don't negate 'build.without?': use 'build.with?'"
  401. end
  402. if line =~ /(not\s|!)\s*build\.without?\?/
  403. problem "Don't negate 'build.with?': use 'build.without?'"
  404. end
  405. if line =~ /ARGV\.(?!(debug\?|verbose\?|value[\(\s]))/
  406. problem "Use build instead of ARGV to check options"
  407. end
  408. if line =~ /def options/
  409. problem "Use new-style option definitions"
  410. end
  411. if line =~ /def test$/
  412. problem "Use new-style test definitions (test do)"
  413. end
  414. if line =~ /MACOS_VERSION/
  415. problem "Use MacOS.version instead of MACOS_VERSION"
  416. end
  417. cats = %w{leopard snow_leopard lion mountain_lion}.join("|")
  418. if line =~ /MacOS\.(?:#{cats})\?/
  419. problem "\"#{$&}\" is deprecated, use a comparison to MacOS.version instead"
  420. end
  421. if line =~ /skip_clean\s+:all/
  422. problem "`skip_clean :all` is deprecated; brew no longer strips symbols\n" +
  423. "\tPass explicit paths to prevent Homebrew from removing empty folders."
  424. end
  425. if line =~ /depends_on [A-Z][\w:]+\.new$/
  426. problem "`depends_on` can take requirement classes instead of instances"
  427. end
  428. if line =~ /^def (\w+).*$/
  429. problem "Define method #{$1.inspect} in the class body, not at the top-level"
  430. end
  431. if line =~ /ENV.fortran/
  432. problem "Use `depends_on :fortran` instead of `ENV.fortran`"
  433. end
  434. if line =~ /depends_on :(.+) (if.+|unless.+)$/
  435. audit_conditional_dep($1.to_sym, $2, $&)
  436. end
  437. if line =~ /depends_on ['"](.+)['"] (if.+|unless.+)$/
  438. audit_conditional_dep($1, $2, $&)
  439. end
  440. if line =~ /(Dir\[("[^\*{},]+")\])/
  441. problem "#{$1} is unnecessary; just use #{$2}"
  442. end
  443. if line =~ /system (["'](#{FILEUTILS_METHODS})["' ])/o
  444. system = $1
  445. method = $2
  446. problem "Use the `#{method}` Ruby method instead of `system #{system}`"
  447. end
  448. if @strict
  449. if line =~ /system (["'][^"' ]*(?:\s[^"' ]*)+["'])/
  450. bad_system = $1
  451. good_system = bad_system.gsub(" ", "\", \"")
  452. problem "Use `system #{good_system}` instead of `system #{bad_system}` "
  453. end
  454. if line =~ /(require ["']formula["'])/
  455. problem "`#{$1}` is now unnecessary"
  456. end
  457. end
  458. end
  459. def audit_conditional_dep(dep, condition, line)
  460. quoted_dep = quote_dep(dep)
  461. dep = Regexp.escape(dep.to_s)
  462. case condition
  463. when /if build\.include\? ['"]with-#{dep}['"]$/, /if build\.with\? ['"]#{dep}['"]$/
  464. problem %{Replace #{line.inspect} with "depends_on #{quoted_dep} => :optional"}
  465. when /unless build\.include\? ['"]without-#{dep}['"]$/, /unless build\.without\? ['"]#{dep}['"]$/
  466. problem %{Replace #{line.inspect} with "depends_on #{quoted_dep} => :recommended"}
  467. end
  468. end
  469. def quote_dep(dep)
  470. Symbol === dep ? dep.inspect : "'#{dep}'"
  471. end
  472. def audit_check_output(output)
  473. problem(output) if output
  474. end
  475. def audit
  476. audit_file
  477. audit_class
  478. audit_specs
  479. audit_urls
  480. audit_deps
  481. audit_conflicts
  482. audit_options
  483. audit_patches
  484. audit_text
  485. text.without_patch.split("\n").each_with_index { |line, lineno| audit_line(line, lineno+1) }
  486. audit_installed
  487. end
  488. private
  489. def problem p
  490. @problems << p
  491. end
  492. def head_only?(formula)
  493. formula.head && formula.stable.nil?
  494. end
  495. end
  496. class ResourceAuditor
  497. attr_reader :problems
  498. attr_reader :version, :checksum, :using, :specs, :url, :name
  499. def initialize(resource)
  500. @name = resource.name
  501. @version = resource.version
  502. @checksum = resource.checksum
  503. @url = resource.url
  504. @using = resource.using
  505. @specs = resource.specs
  506. @problems = []
  507. end
  508. def audit
  509. audit_version
  510. audit_checksum
  511. audit_download_strategy
  512. self
  513. end
  514. def audit_version
  515. if version.nil?
  516. problem "missing version"
  517. elsif version.to_s.empty?
  518. problem "version is set to an empty string"
  519. elsif not version.detected_from_url?
  520. version_text = version
  521. version_url = Version.detect(url, specs)
  522. if version_url.to_s == version_text.to_s && version.instance_of?(Version)
  523. problem "version #{version_text} is redundant with version scanned from URL"
  524. end
  525. end
  526. if version.to_s =~ /^v/
  527. problem "version #{version} should not have a leading 'v'"
  528. end
  529. end
  530. def audit_checksum
  531. return unless checksum
  532. case checksum.hash_type
  533. when :md5
  534. problem "MD5 checksums are deprecated, please use SHA1 or SHA256"
  535. return
  536. when :sha1 then len = 40
  537. when :sha256 then len = 64
  538. end
  539. if checksum.empty?
  540. problem "#{checksum.hash_type} is empty"
  541. else
  542. problem "#{checksum.hash_type} should be #{len} characters" unless checksum.hexdigest.length == len
  543. problem "#{checksum.hash_type} contains invalid characters" unless checksum.hexdigest =~ /^[a-fA-F0-9]+$/
  544. problem "#{checksum.hash_type} should be lowercase" unless checksum.hexdigest == checksum.hexdigest.downcase
  545. end
  546. end
  547. def audit_download_strategy
  548. if url =~ %r[^(cvs|bzr|hg|fossil)://] || url =~ %r[^(svn)\+http://]
  549. problem "Use of the #{$&} scheme is deprecated, pass `:using => :#{$1}` instead"
  550. end
  551. return unless using
  552. if using == :ssl3 || using == CurlSSL3DownloadStrategy
  553. problem "The SSL3 download strategy is deprecated, please choose a different URL"
  554. elsif using == CurlUnsafeDownloadStrategy || using == UnsafeSubversionDownloadStrategy
  555. problem "#{using.name} is deprecated, please choose a different URL"
  556. end
  557. if using == :cvs
  558. mod = specs[:module]
  559. if mod == name
  560. problem "Redundant :module value in URL"
  561. end
  562. if url =~ %r[:[^/]+$]
  563. mod = url.split(":").last
  564. if mod == name
  565. problem "Redundant CVS module appended to URL"
  566. else
  567. problem "Specify CVS module as `:module => \"#{mod}\"` instead of appending it to the URL"
  568. end
  569. end
  570. end
  571. url_strategy = DownloadStrategyDetector.detect(url)
  572. using_strategy = DownloadStrategyDetector.detect('', using)
  573. if url_strategy == using_strategy
  574. problem "Redundant :using value in URL"
  575. end
  576. end
  577. def problem text
  578. @problems << text
  579. end
  580. end