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

#clearvoid

This method returns an undefined value.

Remove the cache file.


76
77
78
79
# File 'lib/rscons/cache.rb', line 76

def clear
  FileUtils.rm_f(CACHE_FILE)
  initialize!
end

#clear_checksum_cache!void

This method returns an undefined value.

Clear the cached file checksums.


84
85
86
# File 'lib/rscons/cache.rb', line 84

def clear_checksum_cache!
  @lookup_checksums = {}
end

#directoriesArray<String>

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

Returns:

  • (Array<String>)

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


242
243
244
# File 'lib/rscons/cache.rb', line 242

def directories
  @cache["directories"].keys
end

#mkdir_p(path) ⇒ void

This method returns an undefined value.

Make any needed directories and record the ones that are created for removal upon a “clean” operation.

Parameters:

  • path (String)

    Directory to create.


225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/rscons/cache.rb', line 225

def mkdir_p(path)
  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(subpath)
      @cache["directories"][subpath] = true
      @dirty = true
    end
  end
end

#register_build(targets, command, deps, env) ⇒ 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)

189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/rscons/cache.rb', line 189

def register_build(targets, command, deps, env)
  Array(targets).each do |target|
    target_checksum = Rscons.phony_target?(target) ? "" : calculate_checksum(target)
    @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,
    }
    @dirty = true
  end
end

#targetsArray<String>

Return a list of targets that have been built.

Returns:

  • (Array<String>)

    List of targets that have been built.


215
216
217
# File 'lib/rscons/cache.rb', line 215

def targets
  @cache["targets"].keys
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):

  • :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


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
# File 'lib/rscons/cache.rb', line 130

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

      return false unless File.exists?(target)
    end

    # target must be registered in the cache
    return false unless @cache["targets"].has_key?(cache_key)

    unless Rscons.phony_target?(target)      # target must have the same checksum as when it was built last

      return false unless @cache["targets"][cache_key]["checksum"] == lookup_checksum(target)
    end

    # command used to build target must be identical
    return false unless @cache["targets"][cache_key]["command"] == Digest::MD5.hexdigest(command.inspect)

    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

      return false unless deps == cached_deps_fnames
    else
      # all dependencies passed in must exist in cache (but cache may have more)
      return false unless (Set.new(deps) - Set.new(cached_deps_fnames)).empty?
    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"] }
    return false unless user_deps == cached_user_deps_fnames

    # all cached dependencies must have their checksums match
    (cached_deps + cached_user_deps).each do |dep_cache|
      return false unless dep_cache["checksum"] == lookup_checksum(dep_cache["fname"])
    end
  end

  true
end

#writevoid

This method returns an undefined value.

Write the cache to disk to be loaded next time.


91
92
93
94
95
96
97
98
99
# File 'lib/rscons/cache.rb', line 91

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