Reducing `winetest.exe` size from ~240MB to ~80MB.
From: Rémi Bernon rbernon@codeweavers.com
--- configure.ac | 10 ++++++++++ tools/wrc/Makefile.in | 2 +- tools/wrc/genres.c | 19 +++++++++++++++++++ tools/wrc/wrc.c | 8 +++++++- tools/wrc/wrc.h | 1 + tools/wrc/wrc.man.in | 4 ++++ 6 files changed, 42 insertions(+), 2 deletions(-)
diff --git a/configure.ac b/configure.ac index f0c7449bdd7..4b9ec3bdc5a 100644 --- a/configure.ac +++ b/configure.ac @@ -84,6 +84,7 @@ AC_ARG_WITH(xshm, AS_HELP_STRING([--without-xshm],[do not use XShm (shared [if test "x$withval" = "xno"; then ac_cv_header_X11_extensions_XShm_h=no; fi]) AC_ARG_WITH(xxf86vm, AS_HELP_STRING([--without-xxf86vm],[do not use XFree video mode extension]), [if test "x$withval" = "xno"; then ac_cv_header_X11_extensions_xf86vmode_h=no; ac_cv_header_X11_extensions_xf86vmproto_h=no; fi]) +AC_ARG_WITH(zlib, AS_HELP_STRING([--without-zlib],[do not use zlib]))
AC_ARG_WITH(system-dllpath,AS_HELP_STRING([--with-system-dllpath=PATH],[load external PE dependencies from colon-separated path PATH]), AC_SUBST(system_dllpath,[$withval])) @@ -1522,6 +1523,15 @@ WINE_NOTICE_WITH(gphoto,[test "$ac_cv_lib_gphoto2_gp_camera_new" != "yes"], WINE_NOTICE_WITH(gphoto,[test "$ac_cv_lib_gphoto2_port_gp_port_info_list_new" != "yes"], [libgphoto2_port ${notice_platform}development files not found, digital cameras won't be auto-detected.])
+dnl **** Check for zlib **** +if test "x$with_zlib" != "xno" +then + WINE_PACKAGE_FLAGS(ZLIB,[zlib],,,, + [AC_CHECK_HEADERS([zlib.h]) + AC_CHECK_LIB(z,compress,[:],[ZLIB_LIBS=""],[$ZLIB_LIBS])]) +fi +WINE_NOTICE_WITH(zlib,[test "$ac_cv_header_zlib_h" != "yes"], + [libz ${notice_platform}development files not found (or too old), wrc compressed resources won't be supported.])
dnl **** Check for resolver library *** if test "$ac_cv_header_resolv_h" = "yes" diff --git a/tools/wrc/Makefile.in b/tools/wrc/Makefile.in index 7e05fd74c24..57a4517a528 100644 --- a/tools/wrc/Makefile.in +++ b/tools/wrc/Makefile.in @@ -1,5 +1,5 @@ PROGRAMS = wrc -UNIX_LIBS = $(GETTEXTPO_LIBS) +UNIX_LIBS = $(GETTEXTPO_LIBS) $(ZLIB_LIBS)
SOURCES = \ genres.c \ diff --git a/tools/wrc/genres.c b/tools/wrc/genres.c index aeec61a06bd..099e70c4ce5 100644 --- a/tools/wrc/genres.c +++ b/tools/wrc/genres.c @@ -34,6 +34,10 @@ #include <assert.h> #include <ctype.h>
+#ifdef HAVE_ZLIB_H +# include <zlib.h> +#endif + #include "../tools.h" #include "wrc.h" #include "utils.h" @@ -959,6 +963,21 @@ static void user2res(name_id_t *name, user_t *usr) put_dword(4); /* ResSize overwritten later*/ }
+#ifdef HAVE_ZLIB_H + if (compress_data) + { + uLongf size = compressBound(usr->data->size); + Bytef *compressed = xmalloc(size); + compress(compressed, &size, (Bytef *)usr->data->data, usr->data->size); + put_dword(0); + put_dword(usr->data->size); + put_data(compressed, size); + free(compressed); + set_res_size(tag); + return; + } +#endif + put_data(usr->data->data, usr->data->size); set_res_size(tag); } diff --git a/tools/wrc/wrc.c b/tools/wrc/wrc.c index 15b63509f74..961d438adb2 100644 --- a/tools/wrc/wrc.c +++ b/tools/wrc/wrc.c @@ -64,6 +64,7 @@ static const char usage[] = " -v, --verbose Enable verbose mode\n" " --verify-translations Check the status of the various translations\n" " --version Print version and exit\n" + " -z Compress the resource with zlib\n" "Input is taken from stdin if no sourcefile specified.\n" "Debug level 'n' is a bitmask with following meaning:\n" " * 0x01 Tell which resource is parsed (verbose mode)\n" @@ -127,6 +128,8 @@ int utf8_input = 0;
int check_utf8 = 1; /* whether to check for valid utf8 */
+int compress_data = 0; + static char *output_name; /* The name given by the -o option */ const char *input_name = NULL; /* The name given on the command-line */ static struct strarray input_files; @@ -165,7 +168,7 @@ enum long_options_values };
static const char short_options[] = - "b:D:Ef:F:hi:I:J:l:m:o:O:ruU:v"; + "b:D:Ef:F:hi:I:J:l:m:o:O:ruU:vz"; static const struct long_option long_options[] = { { "debug", 1, LONG_OPT_DEBUG }, { "define", 1, 'D' }, @@ -376,6 +379,9 @@ static void option_callback( int optc, char *optarg ) case 'v': debuglevel = DEBUGLEVEL_CHAT; break; + case 'z': + compress_data = 1; + break; case '?': fprintf(stderr, "wrc: %s\n\n%s", optarg, usage); exit(1); diff --git a/tools/wrc/wrc.h b/tools/wrc/wrc.h index bb0a0c04e72..f0de11814b5 100644 --- a/tools/wrc/wrc.h +++ b/tools/wrc/wrc.h @@ -40,6 +40,7 @@ extern int preprocess_only; extern int no_preprocess; extern int utf8_input; extern int check_utf8; +extern int compress_data;
extern const char *input_name; extern const char *nlsdirs[]; diff --git a/tools/wrc/wrc.man.in b/tools/wrc/wrc.man.in index ab74ef5fa1d..8cd42427d68 100644 --- a/tools/wrc/wrc.man.in +++ b/tools/wrc/wrc.man.in @@ -135,6 +135,10 @@ Turns on verbose mode (equivalent to \fB-d 1\fR). .TP .I \fB--version\fR Print version and exit. +.TP +.I \fB-z\fR +Compress the resource data, prepending a 32bit zero value followed by the +decompressed size as a 32bit integer. .SH PREPROCESSOR The preprocessor is ANSI-C compatible with some of the extensions of the gcc preprocessor.
From: Rémi Bernon rbernon@codeweavers.com
--- programs/winetest/Makefile.in | 3 ++- programs/winetest/main.c | 46 ++++++++++++++++++++++++++++++++++- tools/makedep.c | 2 +- 3 files changed, 48 insertions(+), 3 deletions(-)
diff --git a/programs/winetest/Makefile.in b/programs/winetest/Makefile.in index 46198ea1017..6f58e2eda48 100644 --- a/programs/winetest/Makefile.in +++ b/programs/winetest/Makefile.in @@ -1,5 +1,6 @@ MODULE = winetest.exe -IMPORTS = uuid comctl32 version user32 gdi32 advapi32 wsock32 msvcrt +IMPORTS = uuid comctl32 version user32 gdi32 advapi32 wsock32 msvcrt $(ZLIB_PE_LIBS) +EXTRAINCL = $(ZLIB_PE_CFLAGS) DELAYIMPORTS = ole32
EXTRADLLFLAGS = -mconsole diff --git a/programs/winetest/main.c b/programs/winetest/main.c index dc4fc4b3581..f1d9e213940 100644 --- a/programs/winetest/main.c +++ b/programs/winetest/main.c @@ -32,6 +32,7 @@ #include <commctrl.h> #include <winternl.h> #include <mshtml.h> +#include <zlib.h>
#include "winetest.h" #include "resource.h" @@ -655,11 +656,45 @@ static void* extract_rcdata (LPCSTR name, LPCSTR type, DWORD* size) return addr; }
+static void *zalloc( void *priv, unsigned int items, unsigned int sz ) +{ + return malloc( items * sz ); +} + +static void zfree( void *priv, void *addr ) +{ + free( addr ); +} + +static void *decompress( const char *res_name, UINT size, + const void *comp, UINT comp_size ) +{ + z_stream z = {.next_in = (BYTE *)comp, .avail_in = comp_size, + .zalloc = zalloc, .zfree = zfree}; + BYTE *data = malloc( size ); + int res; + + res = inflateInit( &z ); + if (res != Z_OK) + report( R_FATAL, "Can't decompress test resource %s: %d", + res_name, GetLastError() ); + + do + { + z.next_out = data + z.total_out; + z.avail_out = size - z.total_out; + res = inflate( &z, Z_FINISH ); + } while (z.avail_in && res == Z_STREAM_END); + + if (res != Z_STREAM_END) inflateEnd( &z ); + return data; +} + /* Fills in the name and exename fields */ static void extract_test (struct wine_test *test, const char *dir, LPSTR res_name) { - BYTE* code; + BYTE* code, *buffer = NULL; DWORD size; char *exepos; HANDLE hfile; @@ -668,6 +703,14 @@ extract_test (struct wine_test *test, const char *dir, LPSTR res_name) code = extract_rcdata (res_name, "TESTRES", &size); if (!code) report (R_FATAL, "Can't find test resource %s: %d", res_name, GetLastError ()); + if (*(DWORD *)code == 0) + { + DWORD comp_size = size - 8; + size = *(DWORD *)(code + 4); + buffer = decompress( res_name, size, code + 8, comp_size ); + code = buffer; + } + test->name = xstrdup( res_name ); test->exename = strmake (NULL, "%s\%s", dir, test->name); exepos = strstr (test->name, testexe); @@ -685,6 +728,7 @@ extract_test (struct wine_test *test, const char *dir, LPSTR res_name) report (R_FATAL, "Failed to write file %s.", test->exename);
CloseHandle(hfile); + free( buffer ); }
static HANDLE get_admin_token(void) diff --git a/tools/makedep.c b/tools/makedep.c index e8ffd2c8395..422a4514eeb 100644 --- a/tools/makedep.c +++ b/tools/makedep.c @@ -3670,7 +3670,7 @@ static void output_test_module( struct makefile *make, unsigned int arch )
output( "programs/winetest/%s%s_test.res: %s\n", arch_dirs[arch], basemodule, obj_dir_path( make, stripped )); - output( "\t%secho "%s_test.exe TESTRES \"%s\"" | %s -u -o $@\n", cmd_prefix( "WRC" ), + output( "\t%secho "%s_test.exe TESTRES \"%s\"" | %s -z -u -o $@\n", cmd_prefix( "WRC" ), basemodule, obj_dir_path( make, stripped ), tools_path( make, "wrc" ));
if (make->disabled[arch] || (parent && parent->disabled[arch]))
Hum, it makes a difference locally but not so much according to the build-winetest job artifacts, I'm probably missing something here but I think it could mitigate the size increase from https://gitlab.winehq.org/wine/wine/-/merge_requests/6672 if doing this is acceptable.
We could probably use upx for the Gitlab CI, but I still think it would be nice to find a solution that doesn't require shipping the entire source.
(Actually it seems to be making a difference, but probably the incremental build didn't rebuild the test executables after the first commit here and they didn't get compressed. Another build here shows a 2x size gain https://gitlab.winehq.org/rbernon/wine/-/jobs/109725/artifacts/browse, even though that build here includes the changes from !6672, so all the test sources included)
This merge request was closed by Rémi Bernon.