Class: Udb::Resolver

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/udb/resolver.rb,
lib/udb/schema.rb

Overview

resolves the specification in the context of a config, and writes to a generation folder

The primary interface for users will be #cfg_arch_for

Defined Under Namespace

Classes: ConfigInfo

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(repo_root = Udb.repo_root, schemas_path_override: nil, cfgs_path_override: nil, gen_path_override: nil, std_path_override: nil, custom_path_override: nil, quiet: false, compile_idl: false)

create a new resolver.

With no arguments, resolver will assume it exists in the riscv-unified-db repository and use standard paths

If repo_root is given, use it as the path to a riscv-unified-db repository

Any specific path can be overridden. If all paths are overridden, it doesn’t matter what repo_root is.

Parameters:

  • repo_root (Pathname) (defaults to: Udb.repo_root)
  • schemas_path_override (Pathname, nil) (defaults to: nil)
  • cfgs_path_override (Pathname, nil) (defaults to: nil)
  • gen_path_override (Pathname, nil) (defaults to: nil)
  • std_path_override (Pathname, nil) (defaults to: nil)
  • custom_path_override (Pathname, nil) (defaults to: nil)
  • quiet (Boolean) (defaults to: false)
  • compile_idl (Boolean) (defaults to: false)


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
# File 'lib/udb/resolver.rb', line 147

def initialize(
  repo_root = Udb.repo_root,
  schemas_path_override: nil,
  cfgs_path_override: nil,
  gen_path_override: nil,
  std_path_override: nil,
  custom_path_override: nil,
  quiet: false,
  compile_idl: false
)
  @repo_root = repo_root
  @schemas_path = schemas_path_override || (@repo_root / "spec" / "schemas")
  @cfgs_path = cfgs_path_override || (@repo_root / "cfgs")
  @gen_path = gen_path_override || (@repo_root / "gen")
  @std_path = std_path_override || (@repo_root / "spec" / "std" / "isa")
  @custom_path = custom_path_override || (@repo_root / "spec" / "custom" / "isa")
  @quiet = quiet
  @compile_idl = compile_idl
  @mutex = Thread::Mutex.new

  # cache of config names
  @cfg_info = T.let(Concurrent::Hash.new, T::Hash[T.any(String, Pathname), ConfigInfo])

  FileUtils.mkdir_p @gen_path
end

Instance Attribute Details

#cfgs_pathPathname (readonly)

path to find configuration files

Returns:

  • (Pathname)


89
90
91
# File 'lib/udb/resolver.rb', line 89

def cfgs_path
  @cfgs_path
end

#custom_pathPathname (readonly)

path to custom overlay specifications

Returns:

  • (Pathname)


101
102
103
# File 'lib/udb/resolver.rb', line 101

def custom_path
  @custom_path
end

#gen_pathPathname (readonly)

path to put generated files into

Returns:

  • (Pathname)


93
94
95
# File 'lib/udb/resolver.rb', line 93

def gen_path
  @gen_path
end

#schemas_pathPathname (readonly)

path to find database schema files

Returns:

  • (Pathname)


85
86
87
# File 'lib/udb/resolver.rb', line 85

def schemas_path
  @schemas_path
end

#std_pathPathname (readonly)

path to the standard specification

Returns:

  • (Pathname)


97
98
99
# File 'lib/udb/resolver.rb', line 97

def std_path
  @std_path
end

Instance Method Details

#any_newer?(target, deps) ⇒ Boolean

returns true if either target does not exist, or if any of deps are newer than target

Parameters:

  • target (Pathname)
  • deps (Array<Pathname>)

Returns:

  • (Boolean)


175
176
177
178
179
180
181
# File 'lib/udb/resolver.rb', line 175

def any_newer?(target, deps)
  if target.exist?
    deps.any? { |d| target.mtime < d.mtime }
  else
    true
  end
end

#cfg_arch_for(config_path_or_name) ⇒ Udb::ConfiguredArchitecture

resolve the specification for a config, and return a ConfiguredArchitecture

Parameters:

  • config_path_or_name (Pathname, String)

Returns:



343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/udb/resolver.rb', line 343

def cfg_arch_for(config_path_or_name)
  config_info = cfg_info(config_path_or_name)

  @cfg_archs ||= Concurrent::Hash.new
  return @cfg_archs[config_info.path] if @cfg_archs.key?(config_info.path)

  resolve_config(config_info.path)
  resolve_arch(config_info.unresolved_yaml)

  @mutex.synchronize do
    return @cfg_archs[config_info.path] if @cfg_archs.key?(config_info.path)

    @cfg_archs[config_info.path] = Udb::ConfiguredArchitecture.new(
      config_info.name,
      Udb::AbstractConfig.create(gen_path / "cfgs" / "#{config_info.name}.yaml", config_info)
    )
  end
end

#cfg_info(config_path_or_name) ⇒ ConfigInfo

Parameters:

  • config_path_or_name (Pathname, String)

Returns:



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/udb/resolver.rb', line 289

def cfg_info(config_path_or_name)
  return @cfg_info.fetch(config_path_or_name) if config_path_or_name.is_a?(String) && @cfg_info.key?(config_path_or_name)
  return @cfg_info.fetch(config_path_or_name.realpath) if config_path_or_name.is_a?(Pathname) && @cfg_info.key?(config_path_or_name.realpath)

  @mutex.synchronize do
    return @cfg_info.fetch(config_path_or_name) if config_path_or_name.is_a?(String) && @cfg_info.key?(config_path_or_name)
    return @cfg_info.fetch(config_path_or_name.realpath) if config_path_or_name.is_a?(Pathname) && @cfg_info.key?(config_path_or_name.realpath)

    config_path =
      case config_path_or_name
      when Pathname
        raise "Path does not exist: #{config_path_or_name}" unless config_path_or_name.file?

        config_path_or_name.realpath
      when String
        (@cfgs_path / "#{config_path_or_name}.yaml").realpath
      else
        T.absurd(config_path_or_name)
      end

    config_yaml = YAML.safe_load_file(config_path)
    if config_yaml.nil?
      puts File.read(config_path)
      raise "Could not load config at #{config_path}"
    end

    overlay_path =
      if config_yaml["arch_overlay"].nil?
        nil
      elsif Pathname.new(config_yaml["arch_overlay"]).exist?
        Pathname.new(config_yaml["arch_overlay"])
      elsif (@custom_path / config_yaml["arch_overlay"]).exist?
        @custom_path / config_yaml["arch_overlay"]
      else
        raise "Cannot resolve path to overlay (#{config_yaml["arch_overlay"]})"
      end

    info = ConfigInfo.new(
      name: config_yaml["name"],
      path: config_path,
      overlay_path:,
      unresolved_yaml: config_yaml,
      spec_path: std_path,
      merged_spec_path: @gen_path / "spec" / (overlay_path.nil? ? "_" : File.basename(overlay_path)),
      resolved_spec_path: @gen_path / "resolved_spec" / (overlay_path.nil? ? "_" : File.basename(overlay_path)),
      resolver: self
    )
    @cfg_info[config_path] = info
    @cfg_info[info.name] = info
  end
end

#merge_arch(config_yaml)

This method returns an undefined value.

Parameters:

  • config_yaml (Hash{String => T.untyped})


222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/udb/resolver.rb', line 222

def merge_arch(config_yaml)
  @mutex.synchronize do
    config_name = config_yaml["name"]

    deps = Dir[std_path / "**" / "*.yaml"].map { |p| Pathname.new(p) }
    deps += Dir[custom_path / config_yaml["arch_overlay"] / "**" / "*.yaml"].map { |p| Pathname.new(p) } unless config_yaml["arch_overlay"].nil?

    overlay_path =
      if config_yaml["arch_overlay"].nil?
        nil
      else
        if config_yaml.fetch("arch_overlay")[0] == "/"
          Pathname.new(config_yaml.fetch("arch_overlay"))
        else
          custom_path / config_yaml.fetch("arch_overlay")
        end
      end
    raise "custom directory '#{overlay_path}' does not exist" if !overlay_path.nil? && !overlay_path.directory?

    if any_newer?(merged_spec_path(config_name) / ".stamp", deps)
      run [
        "uv", "run",
        "#{Udb.gem_path}/python/yaml_resolver.py",
        "merge",
        std_path.to_s,
        overlay_path.nil? ? "/does/not/exist" : overlay_path.to_s,
        merged_spec_path(config_name).to_s
      ]
      FileUtils.touch(merged_spec_path(config_name) / ".stamp")
    end
  end
end

#merged_spec_path(cfg_path_or_name) ⇒ Pathname

path to merged spec (merged with custom overley, but prior to resolution)

Parameters:

  • cfg_path_or_name (String, Pathname)

Returns:

  • (Pathname)


105
106
107
108
109
110
111
112
113
# File 'lib/udb/resolver.rb', line 105

def merged_spec_path(cfg_path_or_name)
  op = cfg_info(cfg_path_or_name).overlay_path
  if op.nil?
    @gen_path / "spec" / "_"
  else
    @gen_path / "spec" / op.basename
  end
  # @gen_path / "spec" / cfg_info(cfg_path_or_name).name
end

#resolve_arch(config_yaml)

This method returns an undefined value.

Parameters:

  • config_yaml (Hash{String => T.untyped})


256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/udb/resolver.rb', line 256

def resolve_arch(config_yaml)
  merge_arch(config_yaml)
  @mutex.synchronize do
    config_name = config_yaml["name"]

    deps = Dir[merged_spec_path(config_name) / "**" / "*.yaml"].map { |p| Pathname.new(p) }
    if any_newer?(resolved_spec_path(config_name) / ".stamp", deps)
      if @compile_idl
        run [
          "uv", "run",
          "#{Udb.gem_path}/python/yaml_resolver.py",
          "resolve",
          "--compile_idl",
          merged_spec_path(config_name).to_s,
          resolved_spec_path(config_name).to_s
        ]
      else
        run [
          "uv", "run",
          "#{Udb.gem_path}/python/yaml_resolver.py",
          "resolve",
          merged_spec_path(config_name).to_s,
          resolved_spec_path(config_name).to_s
        ]
      end
      FileUtils.touch(resolved_spec_path(config_name) / ".stamp")
    end

    FileUtils.cp_r(std_path / "isa", resolved_spec_path(config_name))
  end
end

#resolve_config(config_path) ⇒ Hash{String => T.untyped}

resolve config file and write it to gen_path returns the config data

Parameters:

  • config_path (Pathname)

Returns:

  • (Hash{String => T.untyped})


198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/udb/resolver.rb', line 198

def resolve_config(config_path)
  @mutex.synchronize do
    config_info = cfg_info(config_path)
    return T.must(config_info.resolved_yaml) unless config_info.resolved_yaml.nil?

    resolved_config_yaml = T.let({}, T.nilable(T::Hash[String, T.untyped]))
    # write the config with arch_overlay expanded
    if any_newer?(gen_path / "cfgs" / "#{config_info.name}.yaml", [config_path])
      # is there anything to do here? validate?

      resolved_config_yaml = config_info.unresolved_yaml.dup
      resolved_config_yaml["$source"] = config_path.realpath.to_s

      FileUtils.mkdir_p gen_path / "cfgs"
      File.write(gen_path / "cfgs" / "#{config_info.name}.yaml", YAML.dump(resolved_config_yaml))
    else
      resolved_config_yaml = YAML.load_file(gen_path / "cfgs" / "#{config_info.name}.yaml")
    end

    config_info.resolved_yaml = resolved_config_yaml
  end
end

#resolved_spec_path(cfg_path_or_name) ⇒ Pathname

path to merged and resolved spec

Parameters:

  • cfg_path_or_name (String, Pathname)

Returns:

  • (Pathname)


117
118
119
120
121
122
123
124
125
# File 'lib/udb/resolver.rb', line 117

def resolved_spec_path(cfg_path_or_name)
  # @gen_path / "resolved_spec" / cfg_info(cfg_path_or_name).name
  op = cfg_info(cfg_path_or_name).overlay_path
  if op.nil?
    @gen_path / "resolved_spec" / "_"
  else
    @gen_path / "resolved_spec" / op.basename
  end
end

#run(cmd)

This method returns an undefined value.

run command in the shell. raise if exit is not zero

Parameters:

  • cmd (Array<String>)


185
186
187
188
189
190
191
192
193
# File 'lib/udb/resolver.rb', line 185

def run(cmd)
  puts cmd.join(" ") unless @quiet
  if @quiet
    T.unsafe(self).send(:system, *cmd, out: "/dev/null", err: "/dev/null")
  else
    T.unsafe(self).send(:system, *cmd)
  end
  raise "data resolution error while executing '#{cmd.join(' ')}'" unless $?.success?
end