Module: Vcdeps::Manifest

Defined in:
lib/vcdeps/manifest.rb

Overview

Reads, validates, and canonically hashes a vcpkg manifest directory. The install key (§2.5) is SHA256 over the canonicalized vcpkg.json + vcpkg-configuration.json + triplet + tool version, so the same inputs always map to the same out-of-tree install root and any change forces a fresh one.

Constant Summary collapse

MANIFEST_NAME =
"vcpkg.json"
CONFIGURATION_NAME =
"vcpkg-configuration.json"

Class Method Summary collapse

Class Method Details

.canonical(value) ⇒ Object

Canonical JSON: recursively sort object keys, preserve array order, compact-generate. Two textually different but semantically identical manifests canonicalize identically.



83
84
85
# File 'lib/vcdeps/manifest.rb', line 83

def canonical(value)
  JSON.generate(sort_value(value))
end

.configuration_path(dir) ⇒ Object



22
23
24
# File 'lib/vcdeps/manifest.rb', line 22

def configuration_path(dir)
  File.join(dir, CONFIGURATION_NAME)
end

.key(dir, triplet, tool_version) ⇒ Object

The 16-hex install key for (manifest dir, triplet, tool version) (§2.5).



65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/vcdeps/manifest.rb', line 65

def key(dir, triplet, tool_version)
  manifest = read_json(File.join(dir, MANIFEST_NAME))
  config_path = configuration_path(dir)
  config = File.exist?(config_path) ? read_json(config_path) : nil

  material = [
    canonical(manifest),
    config ? canonical(config) : "",
    triplet.to_s,
    tool_version.to_s
  ].join("\0")

  Digest::SHA256.hexdigest(material)[0, 16]
end

.load!(dir) ⇒ Object

Pre-flight check (§5.1/§5.2): the manifest exists, parses, and either pins "builtin-baseline" OR has a vcpkg-configuration.json beside it (git-registry instances hard-fail otherwise). Returns the parsed manifest Hash.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/vcdeps/manifest.rb', line 43

def load!(dir)
  path = File.join(dir, MANIFEST_NAME)
  manifest = read_json(path)

  unless manifest.is_a?(Hash)
    raise ManifestError, "vcdeps: #{MANIFEST_NAME} must be a JSON object."
  end

  has_baseline = manifest.key?("builtin-baseline") &&
                 !manifest["builtin-baseline"].to_s.empty?
  has_config   = File.exist?(configuration_path(dir))

  unless has_baseline || has_config
    raise ManifestError, "vcdeps: #{manifest_path(dir)} has no " \
      "\"builtin-baseline\" (required by git-registry vcpkg instances). " \
      "Add one with: vcdeps baseline --manifest #{dir.tr('/', '\\')}"
  end

  manifest
end

.manifest_path(dir) ⇒ Object

Absolute path to

\vcpkg.json (display-backslashed).



18
19
20
# File 'lib/vcdeps/manifest.rb', line 18

def manifest_path(dir)
  File.join(dir, MANIFEST_NAME).tr("/", "\\")
end

.read_json(path) ⇒ Object

Read and JSON-parse a file with BOM tolerance (editors love BOMs). Returns the parsed Hash. Raises Vcdeps::ManifestError on a missing file or a JSON syntax error (carrying the parser's position message).



29
30
31
32
33
34
35
36
37
38
# File 'lib/vcdeps/manifest.rb', line 29

def read_json(path)
  raw = File.read(path, encoding: "BOM|UTF-8")
  JSON.parse(raw)
rescue Errno::ENOENT
  raise ManifestError, "vcdeps: no manifest at #{path.tr('/', '\\')}. " \
    "Create a vcpkg.json there (see `vcdeps doctor`)."
rescue JSON::ParserError => e
  raise ManifestError, "vcdeps: #{File.basename(path)} is not valid JSON: " \
    "#{e.message}"
end

.sort_value(value) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/vcdeps/manifest.rb', line 87

def sort_value(value)
  case value
  when Hash
    value.keys.sort.each_with_object({}) do |k, h|
      h[k] = sort_value(value[k])
    end
  when Array
    value.map { |v| sort_value(v) }
  else
    value
  end
end