Class: RuboCop::ResultCache Private
Relationships & Source Files | |
Inherits: | Object |
Defined in: | lib/rubocop/result_cache.rb |
Overview
Provides functionality for caching RuboCop runs.
Constant Summary
-
DL_EXTENSIONS =
# File 'lib/rubocop/result_cache.rb', line 16::RbConfig::CONFIG .values_at('DLEXT', 'DLEXT2') .reject { |ext| !ext || ext.empty? } .map { |ext| ".#{ext}" } .freeze
-
NON_CHANGING =
# File 'lib/rubocop/result_cache.rb', line 12%i[color format formatters out debug fail_level fix_layout autocorrect safe_autocorrect autocorrect_all cache fail_fast stdin parallel].freeze
Class Attribute Summary
- .inhibit_cleanup rw Internal use only
- .rubocop_required_features rw Internal use only
- .source_checksum rw Internal use only
Class Method Summary
- .allow_symlinks_in_cache_location?(config_store) ⇒ Boolean Internal use only
- .cache_root(config_store) Internal use only
-
.cleanup(config_store, verbose, cache_root = nil)
Internal use only
Remove old files so that the cache doesn’t grow too big.
- .new(file, team, options, config_store, cache_root = nil) ⇒ ResultCache constructor Internal use only
- .remove_files(files, dirs, remove_count) private Internal use only
- .remove_oldest_files(files, dirs, cache_root, verbose) private Internal use only
- .requires_file_removal?(file_count, config_store) ⇒ Boolean private Internal use only
Instance Attribute Summary
- #debug? ⇒ Boolean readonly Internal use only
- #path readonly Internal use only
- #valid? ⇒ Boolean readonly Internal use only
Instance Method Summary
- #load Internal use only
- #save(offenses) Internal use only
- #any_symlink?(path) ⇒ Boolean private Internal use only
-
#context_checksum(team, options)
private
Internal use only
We combine team and options into a single "context" checksum to avoid making file names that are too long for some filesystems to handle.
- #digest(path) private Internal use only
- #file_checksum(file, config_store) private Internal use only
-
#relevant_options_digest(options)
private
Internal use only
Return a hash of the options given at invocation, minus the ones that have no effect on which offenses and disabled line ranges are found, and thus don’t affect caching.
-
#rubocop_checksum
private
Internal use only
The checksum of the RuboCop program running the inspection.
- #rubocop_extra_features private Internal use only
- #symlink_protection_triggered?(path) ⇒ Boolean private Internal use only
Class Attribute Details
.inhibit_cleanup (rw)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 170
attr_accessor :source_checksum, :inhibit_cleanup
.rubocop_required_features (rw)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 42
attr_accessor :rubocop_required_features
.source_checksum (rw)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 170
attr_accessor :source_checksum, :inhibit_cleanup
Class Method Details
.allow_symlinks_in_cache_location?(config_store) ⇒ Boolean
# File 'lib/rubocop/result_cache.rb', line 81
def self.allow_symlinks_in_cache_location?(config_store) config_store.for_pwd.for_all_cops['AllowSymlinksInCacheRootDirectory'] end
.cache_root(config_store)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 75
def self.cache_root(config_store) CacheConfig.root_dir do config_store.for_pwd.for_all_cops['CacheRootDirectory'] end end
.cleanup(config_store, verbose, cache_root = nil)
Remove old files so that the cache doesn’t grow too big. When the threshold MaxFilesInCache has been exceeded, the oldest 50% of all the files in the cache are removed. The reason for removing so much is that cleaning should be done relatively seldom, since there is a slight risk that some other RuboCop process was just about to read the file, when there’s parallel execution and the cache is shared.
# File 'lib/rubocop/result_cache.rb', line 28
def self.cleanup(config_store, verbose, cache_root = nil) return if inhibit_cleanup # OPTIMIZE: For faster testing cache_root ||= cache_root(config_store) return unless File.exist?(cache_root) files, dirs = Find.find(cache_root).partition { |path| File.file?(path) } return unless requires_file_removal?(files.length, config_store) remove_oldest_files(files, dirs, cache_root, verbose) end
.remove_files(files, dirs, remove_count) (private)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 65
def remove_files(files, dirs, remove_count) # Batch file deletions, deleting over 130,000+ files will crash # File.delete. files[0, remove_count].each_slice(10_000).each do |files_slice| File.delete(*files_slice) end dirs.each { |dir| Dir.rmdir(dir) if Dir["#{dir}/*"].empty? } end
.remove_oldest_files(files, dirs, cache_root, verbose) (private)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 52
def remove_oldest_files(files, dirs, cache_root, verbose) # Add 1 to half the number of files, so that we remove the file if # there's only 1 left. remove_count = (files.length / 2) + 1 puts "Removing the #{remove_count} oldest files from #{cache_root}" if verbose sorted = files.sort_by { |path| File.mtime(path) } remove_files(sorted, dirs, remove_count) rescue Errno::ENOENT # This can happen if parallel RuboCop invocations try to remove the # same files. No problem. puts $ERROR_INFO if verbose end
.requires_file_removal?(file_count, config_store) ⇒ Boolean
(private)
# File 'lib/rubocop/result_cache.rb', line 48
def requires_file_removal?(file_count, config_store) file_count > 1 && file_count > config_store.for_pwd.for_all_cops['MaxFilesInCache'] end
Instance Attribute Details
#debug? ⇒ Boolean
(readonly)
# File 'lib/rubocop/result_cache.rb', line 100
def debug? @debug end
#path (readonly)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 85
attr_reader :path
#valid? ⇒ Boolean
(readonly)
# File 'lib/rubocop/result_cache.rb', line 104
def valid? File.exist?(@path) end
Instance Method Details
#any_symlink?(path) ⇒ Boolean
(private)
#context_checksum(team, options) (private)
We combine team and options into a single "context" checksum to avoid making file names that are too long for some filesystems to handle. This context is for anything that’s not (1) the RuboCop executable checksum or (2) the inspected file checksum.
# File 'lib/rubocop/result_cache.rb', line 229
def context_checksum(team, ) keys = [team.external_dependency_checksum, ( )] Digest::SHA1.hexdigest(keys.join) end
#digest(path) (private)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 189
def digest(path) content = if path.end_with?(*DL_EXTENSIONS) # Shared libraries often contain timestamps of when # they were compiled and other non-stable data. File.basename(path) else File.binread(path) # mtime not reliable end Zlib.crc32(content).to_s end
#file_checksum(file, config_store) (private)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 157
def file_checksum(file, config_store) digester = Digest::SHA1.new mode = File.stat(file).mode digester.update("#{file}#{mode}#{config_store.for_file(file).signature}") digester.file(file) digester.hexdigest rescue Errno::ENOENT # Spurious files that come and go should not cause a crash, at least not # here. '_' end
#load
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 108
def load puts "Loading cache from #{@path}" if debug? @cached_data.from_json(File.read(@path, encoding: Encoding::UTF_8)) end
#relevant_options_digest(options) (private)
Return a hash of the options given at invocation, minus the ones that have no effect on which offenses and disabled line ranges are found, and thus don’t affect caching.
# File 'lib/rubocop/result_cache.rb', line 220
def ( ) = .reject { |key, _| NON_CHANGING.include?(key) } .to_s.gsub(/[^a-z]+/i, '_') end
#rubocop_checksum (private)
The checksum of the RuboCop program running the inspection.
# File 'lib/rubocop/result_cache.rb', line 174
def rubocop_checksum ResultCache.source_checksum ||= begin digest = Digest::SHA1.new rubocop_extra_features .select { |path| File.file?(path) } .sort! .each do |path| digest << digest(path) end digest << RuboCop::Version::STRING << RuboCop::AST::Version::STRING digest.hexdigest end end
#rubocop_extra_features (private)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 200
def rubocop_extra_features lib_root = File.join(File.dirname(__FILE__), '..') exe_root = File.join(lib_root, '..', 'exe') # Make sure to use an absolute path to prevent errors on Windows # when traversing the relative paths with symlinks. exe_root = File.absolute_path(exe_root) # These are all the files we have `require`d plus everything in the # exe directory. A change to any of them could affect the cop output # so we include them in the cache hash. source_files = $LOADED_FEATURES + Find.find(exe_root).to_a source_files -= ResultCache.rubocop_required_features # Rely on gem versions source_files end
#save(offenses)
[ GitHub ]# File 'lib/rubocop/result_cache.rb', line 113
def save(offenses) dir = File.dirname(@path) begin FileUtils.mkdir_p(dir) rescue Errno::EACCES, Errno::EROFS => e warn "Couldn't create cache directory. Continuing without cache.\n #{e.}" return end preliminary_path = "#{@path}_#{rand(1_000_000_000)}" # RuboCop must be in control of where its cached data is stored. A # symbolic link anywhere in the cache directory tree can be an # indication that a symlink attack is being waged. return if symlink_protection_triggered?(dir) File.open(preliminary_path, 'w', encoding: Encoding::UTF_8) do |f| f.write(@cached_data.to_json(offenses)) end # The preliminary path is used so that if there are multiple RuboCop # processes trying to save data for the same inspected file # simultaneously, the only problem we run in to is a competition who gets # to write to the final file. The contents are the same, so no corruption # of data should occur. FileUtils.mv(preliminary_path, @path) end
#symlink_protection_triggered?(path) ⇒ Boolean
(private)
# File 'lib/rubocop/result_cache.rb', line 142
def symlink_protection_triggered?(path) !@allow_symlinks_in_cache_location && any_symlink?(path) end