|
1
|
/* |
|
2
|
** Copyright (c) 2014 D. Richard Hipp |
|
3
|
** |
|
4
|
** This program is free software; you can redistribute it and/or |
|
5
|
** modify it under the terms of the Simplified BSD License (also |
|
6
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
7
|
** |
|
8
|
** This program is distributed in the hope that it will be useful, |
|
9
|
** but without any warranty; without even the implied warranty of |
|
10
|
** merchantability or fitness for a particular purpose. |
|
11
|
** |
|
12
|
** Author contact information: |
|
13
|
** [email protected] |
|
14
|
** http://www.hwaci.com/drh/ |
|
15
|
** |
|
16
|
******************************************************************************* |
|
17
|
** |
|
18
|
** This is a stand-alone utility program that is part of the Fossil build |
|
19
|
** process. This program reads files named on the command line and converts |
|
20
|
** them into ANSI-C static char array variables. Output is written onto |
|
21
|
** standard output. |
|
22
|
** |
|
23
|
** Additionally, the input files may be listed in a separate list file (one |
|
24
|
** resource name per line, optionally enclosed in double quotes). Pass the list |
|
25
|
** via '--reslist <the-list-file>' option. Both lists, from the command line and |
|
26
|
** the list file, are merged; duplicate file names skipped from processing. |
|
27
|
** This option is useful to get around the command line length limitations |
|
28
|
** under some OS, like Windows. |
|
29
|
** |
|
30
|
** The makefiles use this utility to package various resources (large scripts, |
|
31
|
** GIF images, etc) that are separate files in the source code as byte |
|
32
|
** arrays in the resulting executable. |
|
33
|
*/ |
|
34
|
#include <stdio.h> |
|
35
|
#include <stdlib.h> |
|
36
|
#include <string.h> |
|
37
|
#include <ctype.h> |
|
38
|
|
|
39
|
/* |
|
40
|
** Read the entire content of the file named zFilename into memory obtained |
|
41
|
** from malloc() and return a pointer to that memory. Write the size of the |
|
42
|
** file into *pnByte. |
|
43
|
*/ |
|
44
|
static unsigned char *read_file(const char *zFilename, int *pnByte){ |
|
45
|
FILE *in; |
|
46
|
unsigned char *z; |
|
47
|
int nByte; |
|
48
|
int got; |
|
49
|
in = fopen(zFilename, "rb"); |
|
50
|
if( in==0 ){ |
|
51
|
return 0; |
|
52
|
} |
|
53
|
fseek(in, 0, SEEK_END); |
|
54
|
*pnByte = nByte = ftell(in); |
|
55
|
fseek(in, 0, SEEK_SET); |
|
56
|
z = malloc( nByte+1 ); |
|
57
|
if( z==0 ){ |
|
58
|
fprintf(stderr, "failed to allocate %d bytes\n", nByte+1); |
|
59
|
exit(1); |
|
60
|
} |
|
61
|
got = fread(z, 1, nByte, in); |
|
62
|
fclose(in); |
|
63
|
z[got] = 0; |
|
64
|
return z; |
|
65
|
} |
|
66
|
|
|
67
|
#ifndef FOSSIL_DEBUG |
|
68
|
/* |
|
69
|
** Try to compress a javascript file by removing unnecessary whitespace. |
|
70
|
** |
|
71
|
** Warning: This compression routine does not necessarily work for any |
|
72
|
** arbitrary Javascript source file. But it should work ok for the |
|
73
|
** well-behaved source files in this project. |
|
74
|
*/ |
|
75
|
static void compressJavascript(unsigned char *z, int *pn){ |
|
76
|
int n = *pn; |
|
77
|
int i, j, k; |
|
78
|
for(i=j=0; i<n; i++){ |
|
79
|
unsigned char c = z[i]; |
|
80
|
if( c=='/' && (i==0 || z[i-1]!=':')){ |
|
81
|
if( z[i+1]=='*' ){ |
|
82
|
while( j>0 && (z[j-1]==' ' || z[j-1]=='\t') ){ j--; } |
|
83
|
for(k=i+3; k<n && (z[k]!='/' || z[k-1]!='*'); k++){} |
|
84
|
i = k; |
|
85
|
continue; |
|
86
|
}else if( z[i+1]=='/' ){ |
|
87
|
while( j>0 && (z[j-1]==' ' || z[j-1]=='\t') ){ j--; } |
|
88
|
for(k=i+2; k<n && z[k]!='\n'; k++){} |
|
89
|
i = k-1; |
|
90
|
continue; |
|
91
|
} |
|
92
|
} |
|
93
|
if( c=='\n' ){ |
|
94
|
if( j==0 ) continue; |
|
95
|
while( j>0 && isspace(z[j-1]) ) j--; |
|
96
|
z[j++] = '\n'; |
|
97
|
while( i+1<n && isspace(z[i+1]) ) i++; |
|
98
|
continue; |
|
99
|
} |
|
100
|
z[j++] = c; |
|
101
|
} |
|
102
|
z[j] = 0; |
|
103
|
*pn = j; |
|
104
|
} |
|
105
|
#endif /* FOSSIL_DEBUG */ |
|
106
|
|
|
107
|
/* |
|
108
|
** There is an instance of the following for each file translated. |
|
109
|
*/ |
|
110
|
typedef struct Resource Resource; |
|
111
|
struct Resource { |
|
112
|
char *zName; |
|
113
|
int nByte; |
|
114
|
int idx; |
|
115
|
}; |
|
116
|
|
|
117
|
typedef struct ResourceList ResourceList; |
|
118
|
struct ResourceList { |
|
119
|
Resource *aRes; |
|
120
|
int nRes; |
|
121
|
char *buf; |
|
122
|
long bufsize; |
|
123
|
}; |
|
124
|
|
|
125
|
|
|
126
|
Resource *read_reslist(char *name, ResourceList *list){ |
|
127
|
#define RESLIST_BUF_MAXBYTES (1L<<20) /* 1 MB of text */ |
|
128
|
FILE *in; |
|
129
|
long filesize = 0L; |
|
130
|
long linecount = 0L; |
|
131
|
char *p = 0; |
|
132
|
char *pb = 0; |
|
133
|
|
|
134
|
memset(list, 0, sizeof(*list)); |
|
135
|
|
|
136
|
if( (in = fopen(name, "rb"))==0 ){ |
|
137
|
return list->aRes; |
|
138
|
} |
|
139
|
fseek(in, 0L, SEEK_END); |
|
140
|
filesize = ftell(in); |
|
141
|
rewind(in); |
|
142
|
|
|
143
|
if( filesize > RESLIST_BUF_MAXBYTES ){ |
|
144
|
fprintf(stderr, "List file [%s] must be smaller than %ld bytes\n", name, |
|
145
|
RESLIST_BUF_MAXBYTES); |
|
146
|
return list->aRes; |
|
147
|
} |
|
148
|
list->bufsize = filesize; |
|
149
|
list->buf = (char *)calloc((list->bufsize + 2), sizeof(list->buf[0])); |
|
150
|
if( list->buf==0 ){ |
|
151
|
fprintf(stderr, "failed to allocated %ld bytes\n", list->bufsize + 1); |
|
152
|
list->bufsize = 0L; |
|
153
|
return list->aRes; |
|
154
|
} |
|
155
|
filesize = fread(list->buf, sizeof(list->buf[0]),list->bufsize, in); |
|
156
|
if ( filesize!=list->bufsize ){ |
|
157
|
fprintf(stderr, "failed to read [%s]\n", name); |
|
158
|
return list->aRes; |
|
159
|
} |
|
160
|
fclose(in); |
|
161
|
|
|
162
|
/* |
|
163
|
** append an extra newline (if missing) for a correct line count |
|
164
|
*/ |
|
165
|
if( list->buf[list->bufsize-1]!='\n' ) list->buf[list->bufsize]='\n'; |
|
166
|
|
|
167
|
linecount = 0L; |
|
168
|
for( p = strchr(list->buf, '\n'); |
|
169
|
p && p <= &list->buf[list->bufsize-1]; |
|
170
|
p = strchr(++p, '\n') ){ |
|
171
|
++linecount; |
|
172
|
} |
|
173
|
|
|
174
|
list->aRes = (Resource *)calloc(linecount+1, sizeof(list->aRes[0])); |
|
175
|
for( pb = list->buf, p = strchr(pb, '\n'); |
|
176
|
p && p <= &list->buf[list->bufsize-1]; |
|
177
|
pb = ++p, p = strchr(pb, '\n') ){ |
|
178
|
|
|
179
|
char *path = pb; |
|
180
|
char *pe = p - 1; |
|
181
|
|
|
182
|
/* strip leading and trailing whitespace */ |
|
183
|
while( path < p && isspace(*path) ) ++path; |
|
184
|
while( pe > path && isspace(*pe) ){ |
|
185
|
*pe = '\0'; |
|
186
|
--pe; |
|
187
|
} |
|
188
|
|
|
189
|
/* strip outer quotes */ |
|
190
|
while( path < p && *path=='\"') ++path; |
|
191
|
while( pe > path && *pe=='\"' ){ |
|
192
|
*pe = '\0'; |
|
193
|
--pe; |
|
194
|
} |
|
195
|
*p = '\0'; |
|
196
|
|
|
197
|
/* skip empty path */ |
|
198
|
if( *path ){ |
|
199
|
list->aRes[list->nRes].zName = path; |
|
200
|
++(list->nRes); |
|
201
|
} |
|
202
|
} |
|
203
|
return list->aRes; |
|
204
|
} |
|
205
|
|
|
206
|
void free_reslist(ResourceList *list){ |
|
207
|
if( list ){ |
|
208
|
if( list->buf ) free(list->buf); |
|
209
|
if( list->aRes) free(list->aRes); |
|
210
|
memset(list, 0, sizeof(*list)); |
|
211
|
} |
|
212
|
} |
|
213
|
|
|
214
|
/* |
|
215
|
** Compare two Resource objects for sorting purposes. They sort |
|
216
|
** in zName order so that Fossil can search for resources using |
|
217
|
** a binary search. |
|
218
|
*/ |
|
219
|
typedef int (*QsortCompareFunc)(const void *, const void*); |
|
220
|
|
|
221
|
static int compareResource(const Resource *a, const Resource *b){ |
|
222
|
return strcmp(a->zName, b->zName); |
|
223
|
} |
|
224
|
|
|
225
|
int remove_duplicates(ResourceList *list){ |
|
226
|
char dupNameAsc[64] = "\255"; |
|
227
|
char dupNameDesc[64] = ""; |
|
228
|
Resource dupResAsc; |
|
229
|
Resource dupResDesc; |
|
230
|
Resource *pDupRes; |
|
231
|
int dupcount = 0; |
|
232
|
int i; |
|
233
|
|
|
234
|
if( list->nRes==0 ){ |
|
235
|
return list->nRes; |
|
236
|
} |
|
237
|
|
|
238
|
/* |
|
239
|
** scan for duplicates and assign their names to a string that would sort to |
|
240
|
** the bottom, then re-sort and truncate the duplicates |
|
241
|
*/ |
|
242
|
memset(dupNameAsc, dupNameAsc[0], sizeof(dupNameAsc)-2); |
|
243
|
memset(dupNameDesc, dupNameDesc[0], sizeof(dupNameDesc)-2); |
|
244
|
memset(&dupResAsc, 0, sizeof(dupResAsc)); |
|
245
|
dupResAsc.zName = dupNameAsc; |
|
246
|
memset(&dupResDesc, 0, sizeof(dupResDesc)); |
|
247
|
dupResDesc.zName = dupNameDesc; |
|
248
|
pDupRes = (compareResource(&dupResAsc, &dupResDesc) > 0 |
|
249
|
? &dupResAsc : &dupResDesc); |
|
250
|
|
|
251
|
qsort(list->aRes, list->nRes, sizeof(list->aRes[0]), |
|
252
|
(QsortCompareFunc)compareResource); |
|
253
|
for( i=0; i<list->nRes-1 ; ++i){ |
|
254
|
Resource *res = &list->aRes[i]; |
|
255
|
|
|
256
|
while( i<list->nRes-1 |
|
257
|
&& compareResource(res, &list->aRes[i+1])==0 ){ |
|
258
|
fprintf(stderr, "Skipped a duplicate file [%s]\n", list->aRes[i+1].zName); |
|
259
|
memcpy(&list->aRes[i+1], pDupRes, sizeof(list->aRes[0])); |
|
260
|
++dupcount; |
|
261
|
|
|
262
|
++i; |
|
263
|
} |
|
264
|
} |
|
265
|
if( dupcount == 0){ |
|
266
|
return list->nRes; |
|
267
|
} |
|
268
|
qsort(list->aRes, list->nRes, sizeof(list->aRes[0]), |
|
269
|
(QsortCompareFunc)compareResource); |
|
270
|
list->nRes -= dupcount; |
|
271
|
memset(&list->aRes[list->nRes], 0, sizeof(list->aRes[0])); |
|
272
|
|
|
273
|
return list->nRes; |
|
274
|
} |
|
275
|
|
|
276
|
int main(int argc, char **argv){ |
|
277
|
int i, sz; |
|
278
|
int j, n; |
|
279
|
ResourceList resList; |
|
280
|
Resource *aRes; |
|
281
|
int nRes; |
|
282
|
unsigned char *pData; |
|
283
|
int nErr = 0; |
|
284
|
int nSkip; |
|
285
|
int nPrefix = 0; |
|
286
|
#ifndef FOSSIL_DEBUG |
|
287
|
int nName; |
|
288
|
#endif |
|
289
|
|
|
290
|
if( argc==1 ){ |
|
291
|
fprintf(stderr, "usage\t:%s " |
|
292
|
"[--prefix path] [--reslist file] [resource-file1 ...]\n", |
|
293
|
argv[0] |
|
294
|
); |
|
295
|
return 1; |
|
296
|
} |
|
297
|
if( argc>3 && strcmp(argv[1],"--prefix")==0 ){ |
|
298
|
nPrefix = (int)strlen(argv[2]); |
|
299
|
argc -= 2; |
|
300
|
argv += 2; |
|
301
|
} |
|
302
|
|
|
303
|
memset(&resList, 0, sizeof(resList)); |
|
304
|
if( argc>2 && strcmp(argv[1],"--reslist")==0 ){ |
|
305
|
if( read_reslist(argv[2], &resList)==0 ){ |
|
306
|
fprintf(stderr, "Failed to load resource list from [%s]", argv[2]); |
|
307
|
free_reslist(&resList); |
|
308
|
return 1; |
|
309
|
} |
|
310
|
argc -= 2; |
|
311
|
argv += 2; |
|
312
|
} |
|
313
|
|
|
314
|
if( argc>1 ){ |
|
315
|
aRes = realloc(resList.aRes, (resList.nRes+argc-1)*sizeof(resList.aRes[0])); |
|
316
|
if( aRes==0 || aRes==resList.aRes ){ |
|
317
|
fprintf(stderr, "realloc failed\n"); |
|
318
|
free_reslist(&resList); |
|
319
|
return 1; |
|
320
|
} |
|
321
|
resList.aRes = aRes; |
|
322
|
|
|
323
|
for(i=0; i<argc-1; i++){ |
|
324
|
resList.aRes[resList.nRes].zName = argv[i+1]; |
|
325
|
++resList.nRes; |
|
326
|
} |
|
327
|
} |
|
328
|
|
|
329
|
if( resList.nRes==0 ){ |
|
330
|
fprintf(stderr,"No resource files to process\n"); |
|
331
|
free_reslist(&resList); |
|
332
|
return 1; |
|
333
|
} |
|
334
|
remove_duplicates(&resList); |
|
335
|
|
|
336
|
nRes = resList.nRes; |
|
337
|
aRes = resList.aRes; |
|
338
|
qsort(aRes, nRes, sizeof(aRes[0]), (QsortCompareFunc)compareResource); |
|
339
|
|
|
340
|
printf("/* Automatically generated code: Do not edit.\n**\n" |
|
341
|
"** Rerun the \"mkbuiltin.c\" program or rerun the Fossil\n" |
|
342
|
"** makefile to update this source file.\n" |
|
343
|
"*/\n"); |
|
344
|
for(i=0; i<nRes; i++){ |
|
345
|
pData = read_file(aRes[i].zName, &sz); |
|
346
|
if( pData==0 ){ |
|
347
|
fprintf(stderr, "Cannot open file [%s]\n", aRes[i].zName); |
|
348
|
nErr++; |
|
349
|
continue; |
|
350
|
} |
|
351
|
|
|
352
|
/* Skip initial lines beginning with # */ |
|
353
|
nSkip = 0; |
|
354
|
while( pData[nSkip]=='#' ){ |
|
355
|
while( pData[nSkip]!=0 && pData[nSkip]!='\n' ){ nSkip++; } |
|
356
|
if( pData[nSkip]=='\n' ) nSkip++; |
|
357
|
} |
|
358
|
|
|
359
|
#ifndef FOSSIL_DEBUG |
|
360
|
/* Compress javascript source files */ |
|
361
|
nName = (int)strlen(aRes[i].zName); |
|
362
|
if( (nName>3 && strcmp(&aRes[i].zName[nName-3],".js")==0) |
|
363
|
|| (nName>7 && strcmp(&aRes[i].zName[nName-7], "/js.txt")==0) |
|
364
|
){ |
|
365
|
int x = sz-nSkip; |
|
366
|
compressJavascript(pData+nSkip, &x); |
|
367
|
sz = x + nSkip; |
|
368
|
} |
|
369
|
#endif |
|
370
|
|
|
371
|
aRes[i].nByte = sz - nSkip; |
|
372
|
aRes[i].idx = i; |
|
373
|
printf("/* Content of file %s */\n", aRes[i].zName); |
|
374
|
printf("static const unsigned char bidata%d[%d] = {\n ", |
|
375
|
i, sz+1-nSkip); |
|
376
|
for(j=nSkip, n=0; j<=sz; j++){ |
|
377
|
printf("%3d", pData[j]); |
|
378
|
if( j==sz ){ |
|
379
|
printf(" };\n"); |
|
380
|
}else if( n==14 ){ |
|
381
|
printf(",\n "); |
|
382
|
n = 0; |
|
383
|
}else{ |
|
384
|
printf(", "); |
|
385
|
n++; |
|
386
|
} |
|
387
|
} |
|
388
|
free(pData); |
|
389
|
} |
|
390
|
printf("typedef struct BuiltinFileTable BuiltinFileTable;\n"); |
|
391
|
printf("struct BuiltinFileTable {\n"); |
|
392
|
printf(" const char *zName;\n"); |
|
393
|
printf(" const unsigned char *pData;\n"); |
|
394
|
printf(" int nByte;\n"); |
|
395
|
printf("};\n"); |
|
396
|
printf("static const BuiltinFileTable aBuiltinFiles[] = {\n"); |
|
397
|
for(i=0; i<nRes; i++){ |
|
398
|
char *z = aRes[i].zName; |
|
399
|
if( strlen(z)>=nPrefix ) z += nPrefix; |
|
400
|
while( z[0]=='.' || z[0]=='/' || z[0]=='\\' ){ z++; } |
|
401
|
aRes[i].zName = z; |
|
402
|
while( z[0] ){ |
|
403
|
if( z[0]=='\\' ) z[0] = '/'; |
|
404
|
z++; |
|
405
|
} |
|
406
|
} |
|
407
|
qsort(aRes, nRes, sizeof(aRes[0]), (QsortCompareFunc)compareResource); |
|
408
|
for(i=0; i<nRes; i++){ |
|
409
|
printf(" { \"%s\", bidata%d, %d },\n", |
|
410
|
aRes[i].zName, aRes[i].idx, aRes[i].nByte); |
|
411
|
} |
|
412
|
printf("};\n"); |
|
413
|
free_reslist(&resList); |
|
414
|
return nErr; |
|
415
|
} |
|
416
|
|