v2 / vlib / v / gen / c / embed.v
171 lines · 163 sloc · 6.82 KB · 5be2b1d9cd1f93efc8a776455991080648a51fb9
Raw
1module c
2
3import os
4import rand
5import v.ast
6import v.pref
7
8fn (mut g Gen) should_really_embed_file() bool {
9 if 'embed_only_metadata' in g.pref.compile_defines {
10 return false
11 }
12 return true
13}
14
15fn (mut g Gen) handle_embedded_files_finish() {
16 if g.embedded_files.len > 0 {
17 if g.should_really_embed_file() {
18 g.gen_embedded_data()
19 }
20 g.gen_embedded_metadata()
21 }
22}
23
24// gen_embed_file_struct generates C code for `$embed_file('...')` calls.
25fn (mut g Gen) gen_embed_file_init(mut node ast.ComptimeCall) {
26 $if trace_embed_file ? {
27 eprintln('> gen_embed_file_init ${node.embed_file.apath}')
28 }
29 if g.should_really_embed_file() {
30 file_bytes := os.read_bytes(node.embed_file.apath) or {
31 panic('unable to read file: "${node.embed_file.rpath}')
32 }
33
34 if node.embed_file.compression_type == 'none' {
35 node.embed_file.bytes = file_bytes
36 } else {
37 cache_dir := os.join_path(os.vmodules_dir(), '.cache', 'embed_file')
38 cache_key := rand.ulid()
39 // cache_key := md5.hexhash(node.embed_file.apath)
40 if !os.exists(cache_dir) {
41 os.mkdir_all(cache_dir) or { panic(err) }
42 }
43 cache_path := os.join_path(cache_dir, cache_key)
44
45 vexe := pref.vexe_path()
46 compress_cmd := '${os.quoted_path(vexe)} compress ${node.embed_file.compression_type} ${os.quoted_path(node.embed_file.apath)} ${os.quoted_path(cache_path)}'
47 $if trace_embed_file ? {
48 eprintln('> gen_embed_file_init, compress_cmd: ${compress_cmd}')
49 }
50 result := os.execute(compress_cmd)
51 if result.exit_code != 0 {
52 eprintln('unable to compress file "${node.embed_file.rpath}": ${result.output}')
53 node.embed_file.bytes = file_bytes
54 } else {
55 compressed_bytes := os.read_bytes(cache_path) or {
56 eprintln('unable to read compressed file')
57 {
58 }
59 []u8{}
60 }
61 os.rm(cache_path) or {} // clean up
62 node.embed_file.is_compressed = compressed_bytes.len > 0
63 && compressed_bytes.len < file_bytes.len
64 node.embed_file.bytes = if node.embed_file.is_compressed {
65 compressed_bytes
66 } else {
67 file_bytes
68 }
69 }
70 }
71 if node.embed_file.bytes.len > 5242880 {
72 eprintln('embedding of files >= ~5MB is currently not well supported')
73 }
74 node.embed_file.len = file_bytes.len
75 }
76 ef_idx := node.embed_file.hash()
77 g.write('_v_embed_file_metadata( ${ef_idx}U )')
78 g.file.embedded_files << node.embed_file
79 $if trace_embed_file ? {
80 eprintln('> gen_embed_file_init => _v_embed_file_metadata(${ef_idx:-25}) | ${node.embed_file.apath:-50} | compression: ${node.embed_file.compression_type} | len: ${node.embed_file.len}')
81 }
82}
83
84// gen_embedded_metadata embeds all of the deduplicated metadata in g.embedded_files, into the V target executable,
85// into a single generated function _v_embed_file_metadata, that accepts a hash of the absolute path of the embedded
86// files.
87fn (mut g Gen) gen_embedded_metadata() {
88 if g.pref.parallel_cc {
89 g.extern_out.writeln('extern v__embed_file__EmbedFileData _v_embed_file_metadata(u64 ef_hash);')
90 }
91 g.embedded_data.writeln('v__embed_file__EmbedFileData _v_embed_file_metadata(u64 ef_hash) {')
92 g.embedded_data.writeln('\tv__embed_file__EmbedFileData res;')
93 g.embedded_data.writeln('\tmemset(&res, 0, sizeof(res));')
94 g.embedded_data.writeln('\tswitch(ef_hash) {')
95 for emfile in g.embedded_files {
96 ef_idx := emfile.hash()
97 g.embedded_data.writeln('\t\tcase ${ef_idx}U: {')
98 g.embedded_data.writeln('\t\t\tres.path = ${ctoslit(emfile.rpath)};')
99 if g.should_really_embed_file() {
100 // apath is not needed in production and may leak information
101 g.embedded_data.writeln('\t\t\tres.apath = ${ctoslit('')};')
102 } else {
103 g.embedded_data.writeln('\t\t\tres.apath = ${ctoslit(emfile.apath)};')
104 }
105 if g.should_really_embed_file() {
106 // use function generated in Gen.gen_embedded_data()
107 if emfile.is_compressed {
108 g.embedded_data.writeln('\t\t\tres.compression_type = ${ctoslit(emfile.compression_type)};')
109 g.embedded_data.writeln('\t\t\tres.compressed = v__embed_file__find_index_entry_by_path((voidptr)_v_embed_file_index, ${ctoslit(emfile.rpath)}, ${ctoslit(emfile.compression_type)})->data;')
110 g.embedded_data.writeln('\t\t\tres.uncompressed = NULL;')
111 } else {
112 g.embedded_data.writeln('\t\t\tres.uncompressed = v__embed_file__find_index_entry_by_path((voidptr)_v_embed_file_index, ${ctoslit(emfile.rpath)}, ${ctoslit(emfile.compression_type)})->data;')
113 }
114 } else {
115 g.embedded_data.writeln('\t\t\tres.uncompressed = NULL;')
116 }
117 g.embedded_data.writeln('\t\t\tres.free_compressed = 0;')
118 g.embedded_data.writeln('\t\t\tres.free_uncompressed = 0;')
119 if g.should_really_embed_file() {
120 g.embedded_data.writeln('\t\t\tres.len = ${emfile.len};')
121 } else {
122 file_size := os.file_size(emfile.apath)
123 if file_size > 5242880 {
124 eprintln('Warning: embedding of files >= ~5MB is currently not supported')
125 }
126 g.embedded_data.writeln('\t\t\tres.len = ${file_size};')
127 }
128 g.embedded_data.writeln('\t\t\tbreak;')
129 g.embedded_data.writeln('\t\t} // case ${ef_idx}')
130 }
131 g.embedded_data.writeln('\t\tdefault: builtin___v_panic(_S("unknown embed file"));')
132 g.embedded_data.writeln('\t} // switch')
133 g.embedded_data.writeln('\treturn res;')
134 g.embedded_data.writeln('}')
135}
136
137// gen_embedded_data embeds data into the V target executable.
138fn (mut g Gen) gen_embedded_data() {
139 /*
140 TODO implement support for large files - right now the setup has problems
141 // with even just 10 - 50 MB files - the problem is both in V and C compilers.
142 // maybe we need to write to separate files or have an external tool for large files
143 // like the `rcc` tool in Qt?
144 */
145 // Declare the index before any generated helper references it, to keep MSVC happy.
146 index_len := g.embedded_files.len + 1
147 g.embedded_data.writeln('static const v__embed_file__EmbedFileIndexEntry _v_embed_file_index[${index_len}];')
148 for i, emfile in g.embedded_files {
149 g.embedded_data.write_string('static const unsigned char _v_embed_blob_${i}[${emfile.bytes.len}] = {\n ')
150 for j := 0; j < emfile.bytes.len; j++ {
151 b := emfile.bytes[j].hex()
152 if j < emfile.bytes.len - 1 {
153 g.embedded_data.write_string('0x${b},')
154 } else {
155 g.embedded_data.write_string('0x${b}')
156 }
157 if 0 == ((j + 1) % 16) {
158 g.embedded_data.write_string('\n ')
159 }
160 }
161 g.embedded_data.writeln('\n};')
162 }
163 g.embedded_data.writeln('')
164 g.embedded_data.writeln('static const v__embed_file__EmbedFileIndexEntry _v_embed_file_index[${index_len}] = {')
165 for i, emfile in g.embedded_files {
166 g.embedded_data.writeln('\t{${i}, { .str=(byteptr)("${cestring(emfile.rpath)}"), .len=${emfile.rpath.len}, .is_lit=1 }, { .str=(byteptr)("${cestring(emfile.compression_type)}"), .len=${emfile.compression_type.len}, .is_lit=1 }, (byteptr)_v_embed_blob_${i}},')
167 }
168 g.embedded_data.writeln('\t{-1, { .str=(byteptr)(""), .len=0, .is_lit=1 }, { .str=(byteptr)(""), .len=0, .is_lit=1 }, NULL}')
169 g.embedded_data.writeln('};')
170 // see vlib/v/embed_file/embed_file.v, find_index_entry_by_id/2 and find_index_entry_by_path/2
171}
172