From 65faeb77c520a08f79796c5ec55e28e09d4c4c9c Mon Sep 17 00:00:00 2001 From: Guillaume Poirier-Morency Date: Thu, 27 Oct 2016 15:50:40 -0400 Subject: [PATCH] Provide minimal support for Windows (fix #146) Make both platform-dependant 'gio-unix-2.0' and 'gio-windows-2.0' dependencies optional and use features accordingly via 'GIO_UNIX' and 'GIO_WINDOWS' definitions. Use 'add_project_arguments' instead of 'vala_defines' Disable '--socket' option if 'gio-unix-2.0' is not available. For FastCGI, use 'GLib.Win32InputStream' and 'GLib.Win32OutputStream' and gracefully fallback with basic I/O wrapper. Add basic cross files to target 32 and 64 bit targets with MinGW. --- cross/i686-w64-mingw32.txt | 11 ++++++ cross/x86_64-w64-mingw32.txt | 11 ++++++ docs/meson.build | 2 +- meson.build | 23 ++++++++--- src/valum/meson.build | 1 - src/vsgi/meson.build | 16 ++++++-- src/vsgi/servers/meson.build | 8 +--- src/vsgi/servers/vsgi-cgi.vala | 21 ++++++---- src/vsgi/servers/vsgi-fastcgi.vala | 62 +++++++++++++++++++++++++++--- src/vsgi/vsgi-application.vala | 40 +++++++------------ src/vsgi/vsgi-server.vala | 4 ++ src/vsgi/vsgi-socket-server.vala | 6 ++- tests/fastcgi-server-test.vala | 8 ++++ tests/socket-server-test.vala | 8 ++++ 14 files changed, 165 insertions(+), 56 deletions(-) create mode 100644 cross/i686-w64-mingw32.txt create mode 100644 cross/x86_64-w64-mingw32.txt diff --git a/cross/i686-w64-mingw32.txt b/cross/i686-w64-mingw32.txt new file mode 100644 index 000000000..4aa9bcdb1 --- /dev/null +++ b/cross/i686-w64-mingw32.txt @@ -0,0 +1,11 @@ +[binaries] +exe_wrapper = 'wine' +c = '/usr/bin/i686-w64-mingw32-gcc' +strip = '/usr/bin/i686-w64-mingw32-strip' +pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config' + +[host_machine] +system = 'windows' +cpu_family = 'x86' +cpu = 'i686' +endian = 'little' diff --git a/cross/x86_64-w64-mingw32.txt b/cross/x86_64-w64-mingw32.txt new file mode 100644 index 000000000..5f7444dae --- /dev/null +++ b/cross/x86_64-w64-mingw32.txt @@ -0,0 +1,11 @@ +[binaries] +exe_wrapper = 'wine' +c = '/usr/bin/x86_64-w64-mingw32-gcc' +strip = '/usr/bin/x86_64-w64-mingw32-strip' +pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' + +[host_machine] +system = 'windows' +cpu_family = 'x86_64' +cpu = 'x86_64' +endian = 'little' diff --git a/docs/meson.build b/docs/meson.build index a52a1e5fb..4526deb60 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -4,7 +4,7 @@ if valadoc.found() '--package-version', meson.project_version(), '--pkg=glib-2.0', '--pkg=gio-2.0', '--pkg=gio-unix-2.0', '--pkg=gmodule-2.0', '--pkg=libsoup-2.4', '--pkg=posix', - '--target-glib=2.40', '--force'] + vala_defines + [ '--directory', '@OUTPUT@', '@INPUT@'] + '--target-glib=2.40', '--force', '--directory', '@OUTPUT@', '@INPUT@'] custom_target('API documentation', command: valadoc_command + ['--doclet=html'], input: vsgi_sources + valum_sources, output: 'api', diff --git a/meson.build b/meson.build index a107880fe..74856ed11 100644 --- a/meson.build +++ b/meson.build @@ -12,20 +12,33 @@ add_project_arguments(['--enable-experimental', glib = dependency('glib-2.0', version: '>=2.40') gobject = dependency('gobject-2.0', version: '>=2.40') gio = dependency('gio-2.0', version: '>=2.40') -gio_unix = dependency('gio-unix-2.0', version: '>=2.40') +gio_unix = dependency('gio-unix-2.0', version: '>=2.40', required: false) +gio_windows = dependency('gio-windows-2.0', version: '>=2.40', required: false) gmodule = dependency('gmodule-2.0', version: '>=2.40') soup = dependency('libsoup-2.4', version: '>=2.44') -vala_defines = [] - # provide 'OutputStream.write_all_async' and 'SimpleIOStream' if gio.version().version_compare('>=2.44') - vala_defines += '--define=GIO_2_44' + add_project_arguments('--define=GIO_2_44', language: 'vala') +endif + +# provide 'UnixInputStream', 'UnixOutputStream' and 'UnixSocketAddress' +if gio_unix.found() + add_project_arguments('--define=GIO_UNIX', language: 'vala') +endif + +# provide 'Win32InputStream' and 'Win32OutputStream' +if gio_windows.found() + add_project_arguments('--define=GIO_WINDOWS', language: 'vala') endif # new 'Soup.Server' API if soup.version().version_compare('>=2.48') - vala_defines += '--define=SOUP_2_48' + add_project_arguments('--define=SOUP_2_48', language: 'vala') +endif + +if meson.get_compiler('c').has_function('fork') + add_project_arguments('--define=HAVE_FORK', language: 'vala') endif if meson.get_compiler('c').has_function('memmem') diff --git a/src/valum/meson.build b/src/valum/meson.build index 7b7e03707..c5853d5c4 100644 --- a/src/valum/meson.build +++ b/src/valum/meson.build @@ -27,7 +27,6 @@ valum_sources = files( 'valum-subdomain.vala') valum_lib = library('valum-' + api_version, valum_sources, dependencies: [glib, gobject, gio, soup, vsgi], - vala_args: vala_defines, vala_header: 'valum.h', install: true) diff --git a/src/vsgi/meson.build b/src/vsgi/meson.build index 253b4a08f..96f3576cb 100644 --- a/src/vsgi/meson.build +++ b/src/vsgi/meson.build @@ -19,8 +19,8 @@ vsgi_sources = files( 'vsgi-socket-server.vala', 'vsgi-tee-output-stream.vala') vsgi_lib = library('vsgi-' + api_version, vsgi_sources, - dependencies: [glib, gobject, gio, gio_unix, gmodule, soup], - vala_args: ['--pkg=posix'] + vala_defines, + dependencies: [glib, gobject, gio, gio_unix, gio_windows, gmodule, soup], + vala_args: ['--pkg=posix'], vala_header: 'vsgi.h', install: true, install_rpath: '$ORIGIN/vsgi-@0@/servers'.format(api_version)) @@ -31,9 +31,19 @@ vsgi = declare_dependency(include_directories: include_directories('.'), meson.add_install_script('install.sh') +vsgi_requires_private = ['gmodule-2.0'] + +if gio_unix.found () + vsgi_requires_private += 'gio-unix-2.0' +endif + +if gio_windows.found() + vsgi_requires_private += 'gio-windows-2.0' +endif + pkgconfig = import('pkgconfig') pkgconfig.generate(requires: 'glib-2.0 gobject-2.0 gio-2.0 libsoup-2.4', - requires_private: 'gio-unix-2.0 gmodule-2.0', + requires_private: vsgi_requires_private, libraries: vsgi_lib, version: meson.project_version(), name: 'VSGI', diff --git a/src/vsgi/servers/meson.build b/src/vsgi/servers/meson.build index 38e20edcc..600697731 100644 --- a/src/vsgi/servers/meson.build +++ b/src/vsgi/servers/meson.build @@ -1,12 +1,10 @@ shared_library('vsgi-http', ['vsgi-http.vala'], dependencies: [glib, gobject, gio, soup, vsgi], - vala_args: vala_defines, install: true, install_dir: '@0@/vsgi-@1@/servers'.format(get_option('libdir'), api_version)) shared_library('vsgi-cgi', 'vsgi-cgi.vala', - dependencies: [glib, gobject, gio, gio_unix, soup, vsgi], - vala_args: vala_defines, + dependencies: [glib, gobject, gio, gio_unix, gio_windows, soup, vsgi], install: true, install_dir: '@0@/vsgi-@1@/servers'.format(get_option('libdir'), api_version)) @@ -14,14 +12,12 @@ fcgi = meson.get_compiler('c').find_library('fcgi', required: false) if fcgi.found() fcgi_vapi = meson.get_compiler('vala').find_library('fcgi', dirs: meson.current_source_dir()) shared_library('vsgi-fastcgi', 'vsgi-fastcgi.vala', - dependencies: [glib, gobject, gio, gio_unix, soup, vsgi, fcgi, fcgi_vapi], - vala_args: vala_defines, + dependencies: [glib, gobject, gio, gio_unix, gio_windows, soup, vsgi, fcgi, fcgi_vapi], install: true, install_dir: '@0@/vsgi-@1@/servers'.format(get_option('libdir'), api_version)) endif shared_library('vsgi-scgi', 'vsgi-scgi.vala', dependencies: [glib, gobject, gio, soup, vsgi], - vala_args: vala_defines, install: true, install_dir: '@0@/vsgi-@1@/servers'.format(get_option('libdir'), api_version)) diff --git a/src/vsgi/servers/vsgi-cgi.vala b/src/vsgi/servers/vsgi-cgi.vala index dc783f589..631ef1cd0 100644 --- a/src/vsgi/servers/vsgi-cgi.vala +++ b/src/vsgi/servers/vsgi-cgi.vala @@ -71,11 +71,25 @@ namespace VSGI.CGI { } #if GIO_2_44 +#if GIO_UNIX var connection = new SimpleIOStream (new UnixInputStream (stdin.fileno (), true), new UnixOutputStream (stdout.fileno (), true)); +#elif GIO_WINDOWS + var connection = new SimpleIOStream (new Win32InputStream (stdin, true), + new Win32OutputStream (stdout, true)); #else + throw new IOError.NOT_SUPPORTED ("Support for system-dependant I/O stream is missing."); +#endif +#else +#if GIO_UNIX var connection = new Connection (new UnixInputStream (stdin.fileno (), true), new UnixOutputStream (stdout.fileno (), true)); +#elif GIO_WINDOWS + var connection = new Connection (new Win32InputStream (stdin, true), + new Win32OutputStream (stdout, true)); +#else + throw new IOError.NOT_SUPPORTED ("Support for system-dependant I/O stream is missing."); +#endif #endif var req = new Request.from_cgi_environment (connection, Environ.@get ()); @@ -100,12 +114,5 @@ namespace VSGI.CGI { public override void stop () { // CGI handle a single connection } - - /** - * Forking does not make sense for CGI. - */ - public override Pid fork () { - return 0; - } } } diff --git a/src/vsgi/servers/vsgi-fastcgi.vala b/src/vsgi/servers/vsgi-fastcgi.vala index 769e389db..8dd584a1b 100644 --- a/src/vsgi/servers/vsgi-fastcgi.vala +++ b/src/vsgi/servers/vsgi-fastcgi.vala @@ -51,13 +51,31 @@ namespace VSGI.FastCGI { return "Unknown error code '%d'".printf (error); } - private class StreamInputStream : UnixInputStream { + private class StreamInputStream : +#if GIO_UNIX + UnixInputStream +#elif GIO_WINDOWS + Win32InputStream +#else + InputStream +#endif + { public unowned global::FastCGI.Stream @in { construct; get; } +#if GIO_UNIX public StreamInputStream (int fd, global::FastCGI.Stream @in) { Object (fd: fd, close_fd: false, @in: @in); } +#elif GIO_WINDOWS + public StreamInputStream (void* handle, global::FastCGI.Stream @in) { + Object (handle: handle, close_handle: false, @in: @in); + } +#else + public StreamInputStream (global::FastCGI.Stream @in) { + Object (@in: @in); + } +#endif public override ssize_t read (uint8[] buffer, Cancellable? cancellable = null) throws IOError { var read = this.in.read (buffer); @@ -80,15 +98,33 @@ namespace VSGI.FastCGI { } } - private class StreamOutputStream : UnixOutputStream { + private class StreamOutputStream : +#if GIO_UNIX + UnixOutputStream +#elif GIO_WINDOWS + Win32OutputStream +#else + OutputStream +#endif + { public unowned global::FastCGI.Stream @out { construct; get; } public unowned global::FastCGI.Stream err { construct; get; } +#if GIO_UNIX public StreamOutputStream (int fd, global::FastCGI.Stream @out, global::FastCGI.Stream err) { Object (fd: fd, close_fd: false, @out: @out, err: err); } +#elif GIO_WINDOWS + public StreamOutputStream (void* handle, global::FastCGI.Stream @out, global::FastCGI.Stream err) { + Object (handle: handle, close_handle: false, @out: @out, err: err); + } +#else + public StreamOutputStream (global::FastCGI.Stream @out, global::FastCGI.Stream err) { + Object (@out: @out, err: err); + } +#endif public override ssize_t write (uint8[] buffer, Cancellable? cancellable = null) throws IOError { var written = this.out.put_str (buffer); @@ -165,7 +201,10 @@ namespace VSGI.FastCGI { if (address == null) { fd = global::FastCGI.LISTENSOCK_FILENO; _uris.append (new Soup.URI ("fcgi+fd://%d/".printf (fd))); - } else if (address is UnixSocketAddress) { + } + +#if GIO_UNIX + else if (address is UnixSocketAddress) { var socket_address = address as UnixSocketAddress; fd = global::FastCGI.open_socket (socket_address.path, backlog); @@ -175,7 +214,10 @@ namespace VSGI.FastCGI { } _uris.append (new Soup.URI ("fcgi+unix://%s/".printf (socket_address.path))); - } else if (address is InetSocketAddress) { + } +#endif + + else if (address is InetSocketAddress) { var inet_address = address as InetSocketAddress; if (inet_address.get_family () == SocketFamily.IPV6) { @@ -287,8 +329,16 @@ namespace VSGI.FastCGI { yield; - this._input_stream = new StreamInputStream (fd, request.in); - this._output_stream = new StreamOutputStream (fd, request.out, request.err); +#if GIO_UNIX + _input_stream = new StreamInputStream (fd, request.in); + _output_stream = new StreamOutputStream (fd, request.out, request.err); +#elif GIO_WINDOWS + _input_stream = new StreamInputStream ((void*) fd, request.in); + _output_stream = new StreamOutputStream ((void*) fd, request.out, request.err); +#else + _input_stream = new StreamInputStream (request.in); + _output_stream = new StreamOutputStream (request.out, request.err); +#endif return true; } diff --git a/src/vsgi/vsgi-application.vala b/src/vsgi/vsgi-application.vala index 4aef280c8..71c7c020d 100644 --- a/src/vsgi/vsgi-application.vala +++ b/src/vsgi/vsgi-application.vala @@ -49,8 +49,10 @@ public class VSGI.Application : GLib.Application { {"any", 'A', 0, OptionArg.NONE, null, "Listen on any address instead of only from the loopback interface"}, {"ipv4-only", '4', 0, OptionArg.NONE, null, "Listen only to IPv4 interfaces"}, {"ipv6-only", '6', 0, OptionArg.NONE, null, "Listen only to IPv6 interfaces"}, +#if GIO_UNIX // socket {"socket", 's', 0, OptionArg.FILENAME_ARRAY, null, "Listen on each UNIX socket paths", "[]"}, +#endif // file descriptor {"file-descriptor", 'f', 0, OptionArg.STRING_ARRAY, null, "Listen on each file descriptors", "[]"}, {null} @@ -91,7 +93,9 @@ public class VSGI.Application : GLib.Application { var any = options.lookup_value ("any", VariantType.BOOLEAN); var ipv4_only = options.lookup_value ("ipv4-only", VariantType.BOOLEAN); var ipv6_only = options.lookup_value ("ipv6-only", VariantType.BOOLEAN); +#if GIO_UNIX var sockets = options.lookup_value ("socket", VariantType.BYTESTRING_ARRAY); +#endif var fds = options.lookup_value ("file-descriptor", VariantType.STRING_ARRAY); if (addresses != null) { @@ -144,12 +148,14 @@ public class VSGI.Application : GLib.Application { } } +#if GIO_UNIX // socket path if (sockets != null) { foreach (var socket in sockets.get_bytestring_array ()) { server.listen (new UnixSocketAddress (socket)); } } +#endif // file descriptor if (fds != null) { @@ -164,7 +170,12 @@ public class VSGI.Application : GLib.Application { } // default listening interface - if (addresses == null && ports == null && sockets == null && fds == null) { + if (addresses == null && + ports == null && +#if GIO_UNIX + sockets == null && +#endif + fds == null) { server.listen (); } @@ -173,31 +184,6 @@ public class VSGI.Application : GLib.Application { return 1; } - if (options.lookup_value ("forks", VariantType.INT32) != null) { - var forks = options.lookup_value ("forks", VariantType.INT32).get_int32 (); - try { - for (var i = 0; i < forks; i++) { - var pid = server.fork (); - - // worker - if (pid == 0) { - break; - } - - // parent - else { - // monitor child process - ChildWatch.add (pid, (pid, status) => { - warning ("Worker %d exited with status '%d'.", pid, status); - }); - } - } - } catch (Error err) { - critical ("%s (%s, %d)", err.message, err.domain.to_string (), err.code); - return 1; - } - } - foreach (var uri in server.uris) { message ("Listening on '%s'.", uri.to_string (false)); } @@ -205,12 +191,14 @@ public class VSGI.Application : GLib.Application { // keep the process (and workers) alive hold (); +#if GIO_UNIX // release on 'SIGTERM' Unix.signal_add (ProcessSignal.TERM, () => { release (); server.stop (); return false; }, Priority.LOW); +#endif return 0; } diff --git a/src/vsgi/vsgi-server.vala b/src/vsgi/vsgi-server.vala index f76d176a2..4a7a629fd 100644 --- a/src/vsgi/vsgi-server.vala +++ b/src/vsgi/vsgi-server.vala @@ -143,11 +143,15 @@ namespace VSGI { */ [Version (since = "0.3", experimental = true)] public virtual Pid fork () throws Error { +#if HAVE_FORK var pid = Posix.fork (); if (pid == -1) { throw new SpawnError.FORK (strerror (errno)); } return pid; +#else + return 0; +#endif } /** diff --git a/src/vsgi/vsgi-socket-server.vala b/src/vsgi/vsgi-socket-server.vala index bb5d983ee..2d2e77fe3 100644 --- a/src/vsgi/vsgi-socket-server.vala +++ b/src/vsgi/vsgi-socket-server.vala @@ -74,10 +74,14 @@ public abstract class VSGI.SocketServer : Server { effective_inet_address.get_address ().to_string (), effective_inet_address.get_port ()))); } - } else if (effective_address is UnixSocketAddress) { + } + +#if GIO_UNIX + else if (effective_address is UnixSocketAddress) { var effective_unix_address = effective_address as UnixSocketAddress; _uris.append (new Soup.URI ("%s+unix://%s/".printf (scheme, effective_unix_address.get_path ()))); } +#endif } } diff --git a/tests/fastcgi-server-test.vala b/tests/fastcgi-server-test.vala index 94beb4cb7..d9d97ae8a 100644 --- a/tests/fastcgi-server-test.vala +++ b/tests/fastcgi-server-test.vala @@ -36,6 +36,7 @@ public int main (string[] args) { }); Test.add_func ("/fastcgi_server/socket", () => { +#if GIO_UNIX var server = Server.@new ("fastcgi"); try { @@ -48,9 +49,13 @@ public int main (string[] args) { assert (1 == server.uris.length ()); assert ("fcgi+unix://some-socket.sock/" == server.uris.data.to_string (false)); +#else + Test.skip ("This test require 'gio-unix-2.0' installed."); +#endif }); Test.add_func ("/fastcgi_server/multiple_listen", () => { +#if GIO_UNIX var server = Server.@new ("fastcgi"); try { @@ -68,6 +73,9 @@ public int main (string[] args) { } finally { FileUtils.unlink ("some-socket.sock"); } +#else + Test.skip ("This test require 'gio-unix-2.0' installed."); +#endif }); return Test.run (); diff --git a/tests/socket-server-test.vala b/tests/socket-server-test.vala index e52c862f3..5dc7f9624 100644 --- a/tests/socket-server-test.vala +++ b/tests/socket-server-test.vala @@ -59,6 +59,7 @@ public int main (string[] args) { }); Test.add_func ("/socket_server/listen/unix_socket", () => { +#if GIO_UNIX var server = new MockedSocketServer (); try { @@ -70,9 +71,13 @@ public int main (string[] args) { } assert ("mock+unix://some-socket.sock/" == server.uris.data.to_string (false)); +#else + Test.skip ("This test require 'gio-unix-2.0' installed."); +#endif }); Test.add_func ("/socket_server/listen_socket", () => { +#if GIO_UNIX var server = new MockedSocketServer (); try { @@ -84,6 +89,9 @@ public int main (string[] args) { } finally { FileUtils.unlink ("some-socket.sock"); } +#else + Test.skip ("This test require 'gio-unix-2.0' installed."); +#endif }); return Test.run ();