Class: Rscons::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/rscons/cache.rb

Overview

The Cache class keeps track of file checksums, build target commands and dependencies in a JSON file which persists from one invocation to the next. Example cache:

{
  "version" => "1.2.3",
  "targets" => {
    "program" => {
      "checksum" => "A1B2C3D4",
      "command" => "13543518FE",
      "deps" => [
        {
          "fname" => "program.o",
          "checksum" => "87654321",
        },
      ],
      "user_deps" => [
        {
          "fname" => "lscript.ld",
          "checksum" => "77551133",
        },
      ],
    },
    "program.o" => {
      "checksum" => "87654321",
      "command" => "98765ABCD",
      "deps" => [
        {
          "fname" => "program.c",
          "checksum" => "456789ABC",
        },
        {
          "fname" => "program.h",
          "checksum" => "7979764643",
        },
      ],
      "user_deps" => [],
    }
  },
  "directories" => {
    "build" => true,
    "build/one" => true,
    "build/two" => true,
  },
}

Constant Summary collapse

CACHE_FILE =

Name of the file to store cache information in

".rsconscache"
PHONY_PREFIX =

Prefix for phony cache entries.

":PHONY:"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCache

Create a Cache object and load in the previous contents from the cache file.



69
70
71
# File 'lib/rscons/cache.rb', line 69

def initialize
  initialize!
end

Class Method Details

.instanceObject

Access the singleton instance.



62
63
64
# File 'lib/rscons/cache.rb', line 62

def instance
  @instance ||= Cache.new
end

Instance Method Details

#[](key) ⇒ Object

Access cache value.



74
75
76
# File 'lib/rscons/cache.rb', line 74

def [](key)
  @cache[key]
end

#[]=(key, value) ⇒ Object

Assign cache value.



79
80
81
# File 'lib/rscons/cache.rb', line 79

def []=(key, value)
  @cache[key] = value
end

#clearvoid

This method returns an undefined value.

Remove the cache file.



86
87
88
89
# File 'lib/rscons/cache.rb', line 86

def clear
  FileUtils.rm_f(CACHE_FILE)
  initialize!
end

#clear_checksum_cache!void

This method returns an undefined value.

Clear the cached file checksums.



94
95
96
# File 'lib/rscons/cache.rb', line 94

def clear_checksum_cache!
  @lookup_checksums = {}
end

#directories(install) ⇒ Array<String>

Return a list of directories which were created as a part of the build.

Parameters:

  • install (Boolean)

    Whether to return installed directories. If false, will only return normal build directories and not install targets.

Returns:

  • (Array<String>)

    List of directories which were created as a part of the build.



322
323
324
325
326
327
# File 'lib/rscons/cache.rb', line 322

def directories(install)
  install = !!install
  @cache["directories"].select do |key, d_install|
    d_install == install
  end.map(&:first)
end

#mkdir_p(path, options = {}) ⇒ void

This method returns an undefined value.

Create any needed directory components for a build or install operation.

Build directories will be removed if empty upon a “clean” operation. Install directories will be removed if empty upon an “uninstall” operation.

Parameters:

  • path (String)

    Directory to create.

  • options (Hash) (defaults to: {})

    Optional arguments.

Options Hash (options):

  • :install (Boolean)

    Whether the directory is for an install operation.



302
303
304
305
306
307
308
309
310
311
312
# File 'lib/rscons/cache.rb', line 302

def mkdir_p(path, options = {})
  parts = path.split(/[\\\/]/)
  parts.each_index do |i|
    next if parts[i] == ""
    subpath = File.join(*parts[0, i + 1])
    unless File.exists?(subpath)
      FileUtils.mkdir_p(subpath)
      @cache["directories"][subpath] = !!options[:install]
    end
  end
end

#register_build(targets, command, deps, env, options = {}) ⇒ void

This method returns an undefined value.

Store cache information about target(s) built by a builder.

Parameters:

  • targets (Symbol, String, Array<String>)

    The name of the target(s) built.

  • command (String, Array, Hash)

    The command used to build the target. The command parameter can actually be a String, Array, or Hash and could contain information other than just the actual command used to build the target. For the purposes of the Cache, any difference in the command argument will trigger a rebuild.

  • deps (Array<String>)

    List of dependencies for the target.

  • env (Environment)
  • options (Hash) (defaults to: {})

    Optional arguments.

Options Hash (options):

  • :install (Boolean)

    Whether the target is for an install operation.

  • :side_effect (Boolean)

    Whether the target is a side-effect file (no checksum will be stored).



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/rscons/cache.rb', line 245

def register_build(targets, command, deps, env, options = {})
  Array(targets).each do |target|
    target_checksum =
      if options[:side_effect] or Rscons.phony_target?(target)
        ""
      else
        calculate_checksum(target)
      end
    @cache["targets"][get_cache_key(target)] = {
      "command" => Digest::MD5.hexdigest(command.inspect),
      "checksum" => target_checksum,
      "deps" => deps.map do |dep|
        {
          "fname" => dep,
          "checksum" => lookup_checksum(dep),
        }
      end,
      "user_deps" => (env.get_user_deps(target) || []).map do |dep|
        {
          "fname" => dep,
          "checksum" => lookup_checksum(dep),
        }
      end,
      "install" => !!options[:install],
    }
  end
end

#remove_directory(directory) ⇒ void

This method returns an undefined value.

Remove a directory from the cache.



339
340
341
# File 'lib/rscons/cache.rb', line 339

def remove_directory(directory)
  @cache["directories"].delete(directory)
end

#remove_target(target) ⇒ void

This method returns an undefined value.

Remove a target from the cache.



332
333
334
# File 'lib/rscons/cache.rb', line 332

def remove_target(target)
  @cache["targets"].delete(target)
end

#targets(install) ⇒ Array<String>

Return a list of targets that have been built or installed.

Parameters:

  • install (Boolean)

    Whether to return installed targets. If false, will only return normal build targets and not install targets.

Returns:

  • (Array<String>)

    List of build targets that have been built or installed.



281
282
283
284
285
286
# File 'lib/rscons/cache.rb', line 281

def targets(install)
  install = !!install
  @cache["targets"].select do |key, target|
    target["install"] == install
  end.map(&:first)
end

#up_to_date?(targets, command, deps, env, options = {}) ⇒ Boolean

Check if target(s) are up to date.

Parameters:

  • targets (Symbol, String, Array<String>)

    The name(s) of the target file(s).

  • command (String, Array, Hash)

    The command used to build the target. The command parameter can actually be a String, Array, or Hash and could contain information other than just the actual command used to build the target. For the purposes of the Cache, any difference in the command argument will trigger a rebuild.

  • deps (Array<String>)

    List of the target's dependency files.

  • env (Environment)

    The Rscons::Environment.

  • options (Hash) (defaults to: {})

    Optional options.

Options Hash (options):

  • :debug (Boolean)

    If turned on, this causes the Cache to print messages explaining why a build target is out of date. This could aid a builder author in debugging the operation of their builder.

  • :strict_deps (Boolean)

    Only consider a target up to date if its list of dependencies is exactly equal (including order) to the cached list of dependencies

Returns:

  • (Boolean)

    True value if the targets are all up to date, meaning that, for each target:

    • the target exists on disk

    • the cache has information for the target

    • the target's checksum matches its checksum when it was last built

    • the command used to build the target is the same as last time

    • all dependencies listed are also listed in the cache, or, if :strict_deps was given in options, the list of dependencies is exactly equal to those cached

    • each cached dependency file's current checksum matches the checksum stored in the cache file



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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/rscons/cache.rb', line 141

def up_to_date?(targets, command, deps, env, options = {})
  Array(targets).each do |target|
    cache_key = get_cache_key(target)

    unless Rscons.phony_target?(target)
      # target file must exist on disk
      unless File.exists?(target)
        if options[:debug]
          puts "Target #{target} needs rebuilding because it does not exist on disk"
        end
        return false
      end
    end

    # target must be registered in the cache
    unless @cache["targets"].has_key?(cache_key)
      if options[:debug]
        puts "Target #{target} needs rebuilding because there is no cached build information for it"
      end
      return false
    end

    unless Rscons.phony_target?(target)
      # target must have the same checksum as when it was built last
      unless @cache["targets"][cache_key]["checksum"] == lookup_checksum(target)
        if options[:debug]
          puts "Target #{target} needs rebuilding because it has been changed on disk since being built last"
        end
        return false
      end
    end

    # command used to build target must be identical
    unless @cache["targets"][cache_key]["command"] == Digest::MD5.hexdigest(command.inspect)
      if options[:debug]
        puts "Target #{target} needs rebuilding because the command used to build it has changed"
      end
      return false
    end

    cached_deps = @cache["targets"][cache_key]["deps"] || []
    cached_deps_fnames = cached_deps.map { |dc| dc["fname"] }
    if options[:strict_deps]
      # depedencies passed in must exactly equal those in the cache
      unless deps == cached_deps_fnames
        if options[:debug]
          puts "Target #{target} needs rebuilding because the :strict_deps option is given and the set of dependencies does not match the previous set of dependencies"
        end
        return false
      end
    else
      # all dependencies passed in must exist in cache (but cache may have more)
      unless (Set.new(deps) - Set.new(cached_deps_fnames)).empty?
        if options[:debug]
          puts "Target #{target} needs rebuilding because there are new dependencies"
        end
        return false
      end
    end

    # set of user dependencies must match
    user_deps = env.get_user_deps(target) || []
    cached_user_deps = @cache["targets"][cache_key]["user_deps"] || []
    cached_user_deps_fnames = cached_user_deps.map { |dc| dc["fname"] }
    unless user_deps == cached_user_deps_fnames
      if options[:debug]
        puts "Target #{target} needs rebuilding because the set of user-specified dependency files has changed"
      end
      return false
    end

    # all cached dependencies must have their checksums match
    (cached_deps + cached_user_deps).each do |dep_cache|
      unless dep_cache["checksum"] == lookup_checksum(dep_cache["fname"])
        if options[:debug]
          puts "Target #{target} needs rebuilding because dependency file #{dep_cache["fname"]} has changed"
        end
        return false
      end
    end
  end

  true
end

#writevoid

This method returns an undefined value.

Write the cache to disk.



101
102
103
104
105
106
# File 'lib/rscons/cache.rb', line 101

def write
  @cache["version"] = VERSION
  File.open(CACHE_FILE, "w") do |fh|
    fh.puts(JSON.dump(@cache))
  end
end