/usr/lib/ruby/vendor_ruby/sass/plugin/staleness_checker.rb is in ruby-sass 3.4.6-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | require 'thread'
module Sass
module Plugin
# The class handles `.s[ca]ss` file staleness checks via their mtime timestamps.
#
# To speed things up two level of caches are employed:
#
# * A class-level dependency cache which stores @import paths for each file.
# This is a long-lived cache that is reused by every StalenessChecker instance.
# * Three short-lived instance-level caches, one for file mtimes,
# one for whether a file is stale during this particular run.
# and one for the parse tree for a file.
# These are only used by a single StalenessChecker instance.
#
# Usage:
#
# * For a one-off staleness check of a single `.s[ca]ss` file,
# the class-level {stylesheet_needs_update?} method
# should be used.
# * For a series of staleness checks (e.g. checking all files for staleness)
# a StalenessChecker instance should be created,
# and the instance-level \{#stylesheet\_needs\_update?} method should be used.
# the caches should make the whole process significantly faster.
# *WARNING*: It is important not to retain the instance for too long,
# as its instance-level caches are never explicitly expired.
class StalenessChecker
@dependencies_cache = {}
@dependency_cache_mutex = Mutex.new
class << self
# TODO: attach this to a compiler instance.
# @private
attr_accessor :dependencies_cache
attr_reader :dependency_cache_mutex
end
# Creates a new StalenessChecker
# for checking the staleness of several stylesheets at once.
#
# @param options [{Symbol => Object}]
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
def initialize(options)
# URIs that are being actively checked for staleness. Protects against
# import loops.
@actively_checking = Set.new
# Entries in the following instance-level caches are never explicitly expired.
# Instead they are supposed to automatically go out of scope when a series of staleness
# checks (this instance of StalenessChecker was created for) is finished.
@mtimes, @dependencies_stale, @parse_trees = {}, {}, {}
@options = Sass::Engine.normalize_options(options)
end
# Returns whether or not a given CSS file is out of date
# and needs to be regenerated.
#
# @param css_file [String] The location of the CSS file to check.
# @param template_file [String] The location of the Sass or SCSS template
# that is compiled to `css_file`.
# @return [Boolean] Whether the stylesheet needs to be updated.
def stylesheet_needs_update?(css_file, template_file, importer = nil)
template_file = File.expand_path(template_file)
begin
css_mtime = File.mtime(css_file)
rescue Errno::ENOENT
return true
end
stylesheet_modified_since?(template_file, css_mtime, importer)
end
# Returns whether a Sass or SCSS stylesheet has been modified since a given time.
#
# @param template_file [String] The location of the Sass or SCSS template.
# @param mtime [Fixnum] The modification time to check against.
# @param importer [Sass::Importers::Base] The importer used to locate the stylesheet.
# Defaults to the filesystem importer.
# @return [Boolean] Whether the stylesheet has been modified.
def stylesheet_modified_since?(template_file, mtime, importer = nil)
importer ||= @options[:filesystem_importer].new(".")
dependency_updated?(mtime).call(template_file, importer)
end
# Returns whether or not a given CSS file is out of date
# and needs to be regenerated.
#
# The distinction between this method and the instance-level \{#stylesheet\_needs\_update?}
# is that the instance method preserves mtime and stale-dependency caches,
# so it's better to use when checking multiple stylesheets at once.
#
# @param css_file [String] The location of the CSS file to check.
# @param template_file [String] The location of the Sass or SCSS template
# that is compiled to `css_file`.
# @return [Boolean] Whether the stylesheet needs to be updated.
def self.stylesheet_needs_update?(css_file, template_file, importer = nil)
new(Plugin.engine_options).stylesheet_needs_update?(css_file, template_file, importer)
end
# Returns whether a Sass or SCSS stylesheet has been modified since a given time.
#
# The distinction between this method and the instance-level \{#stylesheet\_modified\_since?}
# is that the instance method preserves mtime and stale-dependency caches,
# so it's better to use when checking multiple stylesheets at once.
#
# @param template_file [String] The location of the Sass or SCSS template.
# @param mtime [Fixnum] The modification time to check against.
# @param importer [Sass::Importers::Base] The importer used to locate the stylesheet.
# Defaults to the filesystem importer.
# @return [Boolean] Whether the stylesheet has been modified.
def self.stylesheet_modified_since?(template_file, mtime, importer = nil)
new(Plugin.engine_options).stylesheet_modified_since?(template_file, mtime, importer)
end
private
def dependencies_stale?(uri, importer, css_mtime)
timestamps = @dependencies_stale[[uri, importer]] ||= {}
timestamps.each_pair do |checked_css_mtime, is_stale|
if checked_css_mtime <= css_mtime && !is_stale
return false
elsif checked_css_mtime > css_mtime && is_stale
return true
end
end
timestamps[css_mtime] = dependencies(uri, importer).any?(&dependency_updated?(css_mtime))
rescue Sass::SyntaxError
# If there's an error finding dependencies, default to recompiling.
true
end
def mtime(uri, importer)
@mtimes[[uri, importer]] ||=
begin
mtime = importer.mtime(uri, @options)
if mtime.nil?
with_dependency_cache {|cache| cache.delete([uri, importer])}
nil
else
mtime
end
end
end
def dependencies(uri, importer)
stored_mtime, dependencies =
with_dependency_cache {|cache| Sass::Util.destructure(cache[[uri, importer]])}
if !stored_mtime || stored_mtime < mtime(uri, importer)
dependencies = compute_dependencies(uri, importer)
with_dependency_cache do |cache|
cache[[uri, importer]] = [mtime(uri, importer), dependencies]
end
end
dependencies
end
def dependency_updated?(css_mtime)
proc do |uri, importer|
next true if @actively_checking.include?(uri)
begin
@actively_checking << uri
sass_mtime = mtime(uri, importer)
!sass_mtime ||
sass_mtime > css_mtime ||
dependencies_stale?(uri, importer, css_mtime)
ensure
@actively_checking.delete uri
end
end
end
def compute_dependencies(uri, importer)
tree(uri, importer).grep(Tree::ImportNode) do |n|
next if n.css_import?
file = n.imported_file
key = [file.options[:filename], file.options[:importer]]
@parse_trees[key] = file.to_tree
key
end.compact
end
def tree(uri, importer)
@parse_trees[[uri, importer]] ||= importer.find(uri, @options).to_tree
end
# Get access to the global dependency cache in a threadsafe manner.
# Inside the block, no other thread can access the dependency cache.
#
# @yieldparam cache [Hash] The hash that is the global dependency cache
# @return The value returned by the block to which this method yields
def with_dependency_cache
StalenessChecker.dependency_cache_mutex.synchronize do
yield StalenessChecker.dependencies_cache
end
end
end
end
end
|