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.



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

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

#lookup_checksum(file) ⇒ String

Return a file’s checksum, or the previously calculated checksum for the same file.

Parameters:

  • file (String)

    The file name.

Returns:

  • (String)

    The file’s checksum.



291
292
293
# File 'lib/rscons/cache.rb', line 291

def lookup_checksum(file)
  @lookup_checksums[file] || calculate_checksum(file)
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.



265
266
267
268
269
270
271
272
273
274
275
# File 'lib/rscons/cache.rb', line 265

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_p(subpath)
      @cache["directories"][subpath] = 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)


230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/rscons/cache.rb', line 230

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,
    }
  end
end

#targetsArray<String>

Return a list of targets that have been built.

Returns:

  • (Array<String>)

    List of targets that have been built.



255
256
257
# File 'lib/rscons/cache.rb', line 255

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

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



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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/rscons/cache.rb', line 131

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.



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

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