Skip to content

Commit ace6359

Browse files
authored
all: support $embed_file('embed.vv', .zlib) (#12654)
1 parent 0f50ac3 commit ace6359

24 files changed

+408
-69
lines changed

cmd/tools/vcompress.v

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
module main
2+
3+
import compress.zlib
4+
import os
5+
6+
enum CompressionType {
7+
zlib
8+
}
9+
10+
fn main() {
11+
if os.args.len != 5 {
12+
eprintln('v compress <type> <in> <out>')
13+
eprintln('supported types: zlib')
14+
exit(1)
15+
}
16+
compression_type := match os.args[2] {
17+
'zlib' {
18+
CompressionType.zlib
19+
}
20+
else {
21+
eprintln('unsupported type: ${os.args[1]}')
22+
exit(1)
23+
}
24+
}
25+
path := os.args[3]
26+
content := os.read_bytes(path) or {
27+
eprintln('unable to read "$path": $err')
28+
exit(1)
29+
}
30+
compressed := match compression_type {
31+
.zlib {
32+
zlib.compress(content) or {
33+
eprintln('compression error: $err')
34+
exit(1)
35+
}
36+
}
37+
}
38+
out_path := os.args[4]
39+
40+
os.write_file_array(out_path, compressed) or {
41+
eprintln('failed to write "$out_path": $err')
42+
exit(1)
43+
}
44+
}

cmd/v/v.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const (
2020
'build-vbinaries',
2121
'check-md',
2222
'complete',
23+
'compress',
2324
'doc',
2425
'doctor',
2526
'fmt',

doc/docs.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4982,6 +4982,17 @@ executable, increasing your binary size, but making it more self contained
49824982
and thus easier to distribute. In this case, `embedded_file.data()` will cause *no IO*,
49834983
and it will always return the same data.
49844984

4985+
`$embed_file` supports compression of the embedded file when compiling with `-prod`.
4986+
Currently only one compression type is supported: `zlib`
4987+
4988+
```v ignore
4989+
import os
4990+
fn main() {
4991+
embedded_file := $embed_file('v.png', .zlib) // compressed using zlib
4992+
os.write_file('exported.png', embedded_file.to_string()) ?
4993+
}
4994+
```
4995+
49854996
#### `$tmpl` for embedding and parsing V template files
49864997

49874998
V has a simple template language for text and html templates, and they can easily

vlib/v/ast/ast.v

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -646,8 +646,14 @@ pub mut:
646646

647647
pub struct EmbeddedFile {
648648
pub:
649-
rpath string // used in the source code, as an ID/key to the embed
650-
apath string // absolute path during compilation to the resource
649+
rpath string // used in the source code, as an ID/key to the embed
650+
apath string // absolute path during compilation to the resource
651+
compression_type string
652+
pub mut:
653+
// these are set by gen_embed_file_init in v/gen/c/embed
654+
is_compressed bool
655+
bytes []byte
656+
len int
651657
}
652658

653659
// Each V source file is represented by one File structure.
@@ -1542,8 +1548,7 @@ pub:
15421548
is_vweb bool
15431549
vweb_tmpl File
15441550
//
1545-
is_embed bool
1546-
embed_file EmbeddedFile
1551+
is_embed bool
15471552
//
15481553
is_env bool
15491554
env_pos token.Position
@@ -1554,6 +1559,7 @@ pub mut:
15541559
result_type Type
15551560
env_value string
15561561
args []CallArg
1562+
embed_file EmbeddedFile
15571563
}
15581564

15591565
pub struct None {

vlib/v/checker/checker.v

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,21 @@ const int_min = int(0x80000000)
2020
const int_max = int(0x7FFFFFFF)
2121

2222
const (
23-
valid_comptime_if_os = ['windows', 'ios', 'macos', 'mach', 'darwin', 'hpux', 'gnu',
23+
valid_comptime_if_os = ['windows', 'ios', 'macos', 'mach', 'darwin', 'hpux', 'gnu',
2424
'qnx', 'linux', 'freebsd', 'openbsd', 'netbsd', 'bsd', 'dragonfly', 'android', 'solaris',
2525
'haiku', 'serenity', 'vinix']
26-
valid_comptime_if_compilers = ['gcc', 'tinyc', 'clang', 'mingw', 'msvc', 'cplusplus']
27-
valid_comptime_if_platforms = ['amd64', 'i386', 'aarch64', 'arm64', 'arm32', 'rv64', 'rv32']
28-
valid_comptime_if_cpu_features = ['x64', 'x32', 'little_endian', 'big_endian']
29-
valid_comptime_if_other = ['js', 'debug', 'prod', 'test', 'glibc', 'prealloc',
26+
valid_comptime_compression_types = ['none', 'zlib']
27+
valid_comptime_if_compilers = ['gcc', 'tinyc', 'clang', 'mingw', 'msvc', 'cplusplus']
28+
valid_comptime_if_platforms = ['amd64', 'i386', 'aarch64', 'arm64', 'arm32', 'rv64', 'rv32']
29+
valid_comptime_if_cpu_features = ['x64', 'x32', 'little_endian', 'big_endian']
30+
valid_comptime_if_other = ['js', 'debug', 'prod', 'test', 'glibc', 'prealloc',
3031
'no_bounds_checking', 'freestanding', 'threads', 'js_node', 'js_browser', 'js_freestanding']
31-
valid_comptime_not_user_defined = all_valid_comptime_idents()
32-
array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice',
32+
valid_comptime_not_user_defined = all_valid_comptime_idents()
33+
array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice',
3334
'sort', 'contains', 'index', 'wait', 'any', 'all', 'first', 'last', 'pop']
34-
reserved_type_names = ['bool', 'char', 'i8', 'i16', 'int', 'i64', 'byte', 'u16',
35+
reserved_type_names = ['bool', 'char', 'i8', 'i16', 'int', 'i64', 'byte', 'u16',
3536
'u32', 'u64', 'f32', 'f64', 'map', 'string', 'rune']
36-
vroot_is_deprecated_message = '@VROOT is deprecated, use @VMODROOT or @VEXEROOT instead'
37+
vroot_is_deprecated_message = '@VROOT is deprecated, use @VMODROOT or @VEXEROOT instead'
3738
)
3839

3940
fn all_valid_comptime_idents() []string {
@@ -5866,7 +5867,12 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type {
58665867
return ast.string_type
58675868
}
58685869
if node.is_embed {
5869-
c.file.embedded_files << node.embed_file
5870+
// c.file.embedded_files << node.embed_file
5871+
if node.embed_file.compression_type !in checker.valid_comptime_compression_types {
5872+
supported := checker.valid_comptime_compression_types.map('.$it').join(', ')
5873+
c.error('not supported compression type: .${node.embed_file.compression_type}. supported: $supported',
5874+
node.pos)
5875+
}
58705876
return c.table.find_type_idx('v.embed_file.EmbedFileData')
58715877
}
58725878
if node.is_vweb {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
vlib/v/checker/tests/embed_unknown_compress_type.vv:3:10: error: not supported compression type: .this_is_not_a_valid_compression_type. supported: .none, .zlib
2+
1 |
3+
2 | fn main() {
4+
3 | test := $embed_file('embed_unknown_compress_type.vv', .this_is_not_a_valid_compression_type)
5+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6+
4 | println(test.to_string())
7+
5 | }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
fn main() {
3+
test := $embed_file('embed_unknown_compress_type.vv', .this_is_not_a_valid_compression_type)
4+
println(test.to_string())
5+
}

vlib/v/embed_file/decoder.v

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[has_globals]
2+
module embed_file
3+
4+
interface Decoder {
5+
decompress([]byte) ?[]byte
6+
}
7+
8+
struct EmbedFileDecoders {
9+
mut:
10+
decoders map[string]Decoder
11+
}
12+
13+
__global g_embed_file_decoders = &EmbedFileDecoders{}
14+
15+
pub fn register_decoder(compression_type string, decoder Decoder) {
16+
g_embed_file_decoders.decoders[compression_type] = decoder
17+
}

vlib/v/embed_file/embed_file.v

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ pub const (
99
// https://github.com/vlang/rfcs/blob/master/embedding_resources.md
1010
// EmbedFileData encapsulates functionality for the `$embed_file()` compile time call.
1111
pub struct EmbedFileData {
12-
apath string
12+
apath string
13+
compression_type string
1314
mut:
1415
compressed &byte
1516
uncompressed &byte
@@ -29,6 +30,7 @@ pub fn (mut ed EmbedFileData) free() {
2930
unsafe {
3031
ed.path.free()
3132
ed.apath.free()
33+
ed.compression_type.free()
3234
if ed.free_compressed {
3335
free(ed.compressed)
3436
ed.compressed = &byte(0)
@@ -59,25 +61,31 @@ pub fn (original &EmbedFileData) to_bytes() []byte {
5961
pub fn (mut ed EmbedFileData) data() &byte {
6062
if !isnil(ed.uncompressed) {
6163
return ed.uncompressed
64+
}
65+
if isnil(ed.uncompressed) && !isnil(ed.compressed) {
66+
decoder := g_embed_file_decoders.decoders[ed.compression_type] or {
67+
panic('EmbedFileData error: unknown compression of "$ed.path": "$ed.compression_type"')
68+
}
69+
compressed := unsafe { ed.compressed.vbytes(ed.len) }
70+
decompressed := decoder.decompress(compressed) or {
71+
panic('EmbedFileData error: decompression of "$ed.path" failed: $err')
72+
}
73+
unsafe {
74+
ed.uncompressed = &byte(memdup(decompressed.data, ed.len))
75+
}
6276
} else {
63-
if isnil(ed.uncompressed) && !isnil(ed.compressed) {
64-
// TODO implement uncompression
65-
// See also C Gen.gen_embedded_data() where the compression should occur.
66-
ed.uncompressed = ed.compressed
67-
} else {
68-
mut path := os.resource_abs_path(ed.path)
77+
mut path := os.resource_abs_path(ed.path)
78+
if !os.is_file(path) {
79+
path = ed.apath
6980
if !os.is_file(path) {
70-
path = ed.apath
71-
if !os.is_file(path) {
72-
panic('EmbedFileData error: files "$ed.path" and "$ed.apath" do not exist')
73-
}
74-
}
75-
bytes := os.read_bytes(path) or {
76-
panic('EmbedFileData error: "$path" could not be read: $err')
81+
panic('EmbedFileData error: files "$ed.path" and "$ed.apath" do not exist')
7782
}
78-
ed.uncompressed = bytes.data
79-
ed.free_uncompressed = true
8083
}
84+
bytes := os.read_bytes(path) or {
85+
panic('EmbedFileData error: "$path" could not be read: $err')
86+
}
87+
ed.uncompressed = bytes.data
88+
ed.free_uncompressed = true
8189
}
8290
return ed.uncompressed
8391
}
@@ -91,19 +99,20 @@ pub fn (mut ed EmbedFileData) data() &byte {
9199
pub struct EmbedFileIndexEntry {
92100
id int
93101
path string
102+
algo string
94103
data &byte
95104
}
96105

97106
// find_index_entry_by_path is used internally by the V compiler:
98-
pub fn find_index_entry_by_path(start voidptr, path string) &EmbedFileIndexEntry {
107+
pub fn find_index_entry_by_path(start voidptr, path string, algo string) &EmbedFileIndexEntry {
99108
mut x := &EmbedFileIndexEntry(start)
100-
for !(x.path == path || isnil(x.data)) {
109+
for x.id >= 0 && x.data != 0 && (x.algo != algo || x.path != path) {
101110
unsafe {
102111
x++
103112
}
104113
}
105114
$if debug_embed_file_in_prod ? {
106-
eprintln('>> v.embed_file find_index_entry_by_path ${ptr_str(start)}, path: "$path" => ${ptr_str(x)}')
115+
eprintln('>> v.embed_file find_index_entry_by_path ${ptr_str(start)}, id: $x.id, path: "$path", algo: "$algo" => ${ptr_str(x)}')
107116
}
108117
return x
109118
}

vlib/v/gen/c/cgen.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3828,7 +3828,7 @@ fn (mut g Gen) expr(node ast.Expr) {
38283828
}
38293829
ast.Comment {}
38303830
ast.ComptimeCall {
3831-
g.comptime_call(node)
3831+
g.comptime_call(mut node)
38323832
}
38333833
ast.ComptimeSelector {
38343834
g.comptime_selector(node)

0 commit comments

Comments
 (0)