zon-rb 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +12 -0
- data/lib/zon/hasher.rb +176 -0
- data/lib/zon/version.rb +1 -1
- data/lib/zon/zig.rb +1 -0
- data/lib/zon.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 818004dd00ec5169c64a80953c55e2b49f9b92a7691aef46251463e671c8a033
|
|
4
|
+
data.tar.gz: 2519a754dd2e2cc19ee40c8dbfa7c2ae0370e5bd33fc6e69f7dcc0bebd56eb00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e03c8137c996cb03adc77ddebc0afbb95a6cb078f635052eb88dfb2b782365e7ca335aa5436560619b20e246d7693ba96860625943b780deb15c1a2386ef9472
|
|
7
|
+
data.tar.gz: 497d4a8b6b830abe6b533fdbb22407524616f5b44727d7c73d8a0f9e49105a7db6d2941396c3a904e4dcfc029c0f5157eb4dfe1db63b22248032d56052403344
|
data/README.md
CHANGED
|
@@ -114,6 +114,18 @@ manifest = Zon::Zig::Manifest.new o
|
|
|
114
114
|
# ...
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
### Zig Package Hash
|
|
118
|
+
|
|
119
|
+
Given a path `pdir` that points to the root of a Zig package, one can calculate the packages hash using the `Zon::Hasher`:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
hasher = Zon::Zig::Hasher.new pdir
|
|
123
|
+
hasher.hash
|
|
124
|
+
# Hash with the format nnnn-vvvv-XXXX..XXXX
|
|
125
|
+
# e.g.: uuid-0.4.0-oOieIR2AAAChAUVBY4ABjYI1XN0EbVALmiN0JIlggC3i
|
|
126
|
+
result = hasher.result
|
|
127
|
+
```
|
|
128
|
+
|
|
117
129
|
## Development
|
|
118
130
|
|
|
119
131
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/zon/hasher.rb
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require "find"
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module Zon
|
|
6
|
+
module Zig
|
|
7
|
+
##
|
|
8
|
+
# Produces a package hash of the format: $name-$semver-$hashplus
|
|
9
|
+
#
|
|
10
|
+
# Along with 200 bits of a SHA-256, a package hash includes:
|
|
11
|
+
# - package name
|
|
12
|
+
# - package version
|
|
13
|
+
# - id component of the package fingerprint
|
|
14
|
+
# - total unpacked size on disk
|
|
15
|
+
#
|
|
16
|
+
# The following steps are taken to calculate the hash:
|
|
17
|
+
# 1. Read the list of included paths from the manifest, i.e. files that are part of the package.
|
|
18
|
+
# 2. Resolve the paths:
|
|
19
|
+
# - for directories: include all files that are within the directory or a child dir.
|
|
20
|
+
# - for files: include the file
|
|
21
|
+
# - for symlinks: TODO
|
|
22
|
+
# 3. Sort the paths by:
|
|
23
|
+
# 1. Lexographical order
|
|
24
|
+
# 2. by length
|
|
25
|
+
# 4. For every included file:
|
|
26
|
+
# - Calculate a SHA-256 over: RELATIVE_PATH_FROM_PKG_ROOT || 0x0000 || DATA
|
|
27
|
+
# - Record the file size in bytes
|
|
28
|
+
# - TODO: the 0x0000 will probably change in the future
|
|
29
|
+
# 5. Calculate a SHA-256 sum over all calculated file hashes
|
|
30
|
+
# - respecting the previously mentioned sorting rules.
|
|
31
|
+
# 6. Sum up the sizes of all files into a u32 (sizes that don't fit into a 32-bit unsingned integer will be saturated with the value 2**32 - 1)
|
|
32
|
+
# 7. Produce the package hash
|
|
33
|
+
class Hasher
|
|
34
|
+
attr_reader :paths, :total_size, :digest
|
|
35
|
+
|
|
36
|
+
SUPPORTED_ZIG_VERSIONS = ["0.14.0", "0.14.1", "0.15.1", "0.15.2"]
|
|
37
|
+
|
|
38
|
+
def initialize(package_path, manifest_name: "build.zig.zon")
|
|
39
|
+
# Make sure we have a valid path to work with
|
|
40
|
+
@package_path = package_path
|
|
41
|
+
raise ArgumentError, "not a directory '#{@package_path}'" if not Dir.exist? @package_path
|
|
42
|
+
|
|
43
|
+
manifest_path = File.join(@package_path, manifest_name)
|
|
44
|
+
raise ArgumentError, "no manifest '#{manifest_name}' in '#{@package_path}'" if not File.exist? manifest_path
|
|
45
|
+
|
|
46
|
+
@manifest = Zon.parse(File.open(manifest_path))
|
|
47
|
+
raise ArgumentError, "no 'paths' field in ZON manifest '#{manifest_path}'" if not @manifest.key? :paths
|
|
48
|
+
raise ArgumentError, "no 'name' field in ZON manifest '#{manifest_path}'" if not @manifest.key? :name
|
|
49
|
+
raise ArgumentError, "no 'version' field in ZON manifest '#{manifest_path}'" if not @manifest.key? :version
|
|
50
|
+
raise ArgumentError, "no 'fingerprint' field in ZON manifest '#{manifest_path}'" if not @manifest.key? :fingerprint
|
|
51
|
+
|
|
52
|
+
@paths = []
|
|
53
|
+
@results = {}
|
|
54
|
+
@total_size = 0
|
|
55
|
+
@digest = nil
|
|
56
|
+
|
|
57
|
+
@manifest[:paths].each do |path|
|
|
58
|
+
full_path = File.join(@package_path, path)
|
|
59
|
+
|
|
60
|
+
if File.directory? full_path
|
|
61
|
+
f = Find.find(full_path)
|
|
62
|
+
f.each do |p|
|
|
63
|
+
next if File.directory? p
|
|
64
|
+
|
|
65
|
+
pstr = p.delete_prefix(@package_path)[1..]
|
|
66
|
+
@paths.append(pstr) if not @paths.include? pstr
|
|
67
|
+
end
|
|
68
|
+
elsif File.file? full_path or File.symlink? full_path
|
|
69
|
+
pstr = full_path.delete_prefix(@package_path)[1..]
|
|
70
|
+
@paths.append(pstr) if not @paths.include? pstr
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@paths = @paths.sort_by { |str| [str, -str.length] }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def name
|
|
78
|
+
String(@manifest[:name])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def version
|
|
82
|
+
@manifest[:version]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def hash
|
|
86
|
+
@total_size = 0
|
|
87
|
+
@digest = nil
|
|
88
|
+
|
|
89
|
+
# Hash all files individually
|
|
90
|
+
@paths.each do |str|
|
|
91
|
+
@results[str] = Zon::Zig::Hasher::hash_file(@package_path, str)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
sha2 = Digest::SHA2.new
|
|
95
|
+
|
|
96
|
+
@paths.each do |str|
|
|
97
|
+
res = @results[str]
|
|
98
|
+
|
|
99
|
+
@total_size += res[:size]
|
|
100
|
+
sha2.update [res[:digest]].pack('H*')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
@digest = sha2.hexdigest
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Get the ID part of the fingerprint
|
|
108
|
+
def get_id
|
|
109
|
+
@manifest[:fingerprint] & 0xffffffff
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
# Get the calculated, total size of the unpacked package.
|
|
114
|
+
def get_saturated_size
|
|
115
|
+
max_uint32 = 2**32 - 1
|
|
116
|
+
|
|
117
|
+
if @total_size > max_uint32
|
|
118
|
+
max_uint32
|
|
119
|
+
else
|
|
120
|
+
@total_size
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def result
|
|
125
|
+
Zon::Zig::Hasher::make_hash(@digest, name, version, get_id, get_saturated_size)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
# Produces $name-$semver-$hashplus
|
|
130
|
+
#
|
|
131
|
+
# - name is the name field from a build.zig.zon manifest. It is expected
|
|
132
|
+
# to be at most 32 bytes long and a valid Zig identifier.
|
|
133
|
+
# - semver is the version field from a build.zig.zon manifest. It is expected
|
|
134
|
+
# to be at most 32 bytes long.
|
|
135
|
+
# - hashplus is a base64 (urlsafe and nopad) encoded, 33-byte string:
|
|
136
|
+
# - Pacakge ID (4) || Decompressed Size (4) || Digest0, Digest1, ..., Digest24
|
|
137
|
+
def self.make_hash(digest, name, semver, id, size)
|
|
138
|
+
raise ArgumentError, "name '#{name}' is expected to be at most 32 bytes long but it is #{name.bytesize} bytes" if name.bytesize > 32
|
|
139
|
+
raise ArgumentError, "semver '#{semver}' is expected to be at most 32 bytes long but it is #{semver.bytesize} bytes" if semver.bytesize > 32
|
|
140
|
+
|
|
141
|
+
hash_plus = [id].pack("V")
|
|
142
|
+
hash_plus += [size].pack("V")
|
|
143
|
+
hash_plus += [digest].pack('H*')[0..24]
|
|
144
|
+
|
|
145
|
+
"#{name}-#{semver}-#{Base64.urlsafe_encode64(hash_plus, padding: false)}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def self.hash_file(package_path, path)
|
|
149
|
+
sha2 = Digest::SHA2.new
|
|
150
|
+
sha2.update path
|
|
151
|
+
|
|
152
|
+
full_path = File.join(package_path, path)
|
|
153
|
+
|
|
154
|
+
if File.file? full_path
|
|
155
|
+
f = File.open full_path
|
|
156
|
+
data = f.read
|
|
157
|
+
file_size = data.bytesize
|
|
158
|
+
|
|
159
|
+
# Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463
|
|
160
|
+
sha2.update "\x00\x00"
|
|
161
|
+
sha2.update data
|
|
162
|
+
elsif File.symlink? full_path
|
|
163
|
+
link_name = File.readlink full_path
|
|
164
|
+
sha2.update link_name
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# TODO: symlink
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
digest: sha2.hexdigest,
|
|
171
|
+
size: file_size,
|
|
172
|
+
}
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
data/lib/zon/version.rb
CHANGED
data/lib/zon/zig.rb
CHANGED
|
@@ -16,6 +16,7 @@ module Zon
|
|
|
16
16
|
raise "Missing '.name'" if not zon[:name]
|
|
17
17
|
raise "Missing '.version'" if not zon[:version]
|
|
18
18
|
raise "Missing '.fingerprint'" if not zon[:fingerprint]
|
|
19
|
+
raise "Missing '.paths'" if not zon[:paths]
|
|
19
20
|
|
|
20
21
|
if zon[:dependencies]
|
|
21
22
|
zon[:dependencies].each do |key, value|
|
data/lib/zon.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zon-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David P. Sugar
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-10-19 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: This gem allows you to translate Zig Object Notation (ZON) data into
|
|
13
13
|
Ruby objects and vice versa.
|
|
@@ -24,6 +24,7 @@ files:
|
|
|
24
24
|
- README.md
|
|
25
25
|
- Rakefile
|
|
26
26
|
- lib/zon.rb
|
|
27
|
+
- lib/zon/hasher.rb
|
|
27
28
|
- lib/zon/lexer.rb
|
|
28
29
|
- lib/zon/parser.rb
|
|
29
30
|
- lib/zon/serializer.rb
|