validate: Pass information about GstValidate execution over a socket

Instead of trying to parsing stdout, generate json messages and
send them over a socket so that gst-validate-launcher can properly
have informations about gst-validate subprocess execution.
This commit is contained in:
Thibault Saunier 2016-09-01 17:39:38 -03:00
parent 29e5ac1362
commit 2fff14e469
11 changed files with 333 additions and 117 deletions

View file

@ -201,6 +201,10 @@ if test "x$HAVE_CAIRO" != "xyes"; then
AC_MSG_NOTICE([Cairo is needed for the gst-validate-images-tool])
fi
PKG_CHECK_MODULES(JSON_GLIB, json-glib-1.0)
AC_SUBST(JSON_GLIB_LIBS)
AC_SUBST(JSON_GLIB_CFLAGS)
dnl checks for gstreamer
AG_GST_CHECK_GST_CHECK($GST_API_VERSION, [$GST_REQ], no)

View file

@ -50,14 +50,14 @@ lib_LTLIBRARIES = libgstvalidate-@GST_API_VERSION@.la
libgstvalidate_@GST_API_VERSION@_la_SOURCES = $(source_c)
libgstvalidate_@GST_API_VERSION@include_HEADERS = $(source_h)
libgstvalidate_@GST_API_VERSION@_la_CFLAGS = $(GST_ALL_CFLAGS)\
$(GIO_CFLAGS) $(GST_PBUTILS_CFLAGS) \
$(JSON_GLIB_CFLAGS) $(GIO_CFLAGS) $(GST_PBUTILS_CFLAGS) \
-DGST_USE_UNSTABLE_API
libgstvalidate_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) \
$(GST_LT_LDFLAGS) $(GIO_LDFLAGS) $(GST_PBUTILS_LDFAGS)
libgstvalidate_@GST_API_VERSION@_la_LIBADD = \
$(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) \
$(GST_ALL_LIBS) $(GIO_LIBS) $(GST_PBUTILS_LIBS) \
$(GLIB_LIBS) $(LIBM)
$(JSON_GLIB_LIBS) $(GLIB_LIBS) $(LIBM)
libgstvalidate_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/gst/validate
@ -71,9 +71,9 @@ libgstvalidateplugin_@GST_API_VERSION@_la_CFLAGS = $(GST_ALL_CFLAGS)\
libgstvalidateplugin_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) \
$(GST_LT_LDFLAGS) $(GIO_LDFLAGS) $(GST_PBUTILS_LDFAGS)
libgstvalidateplugin_@GST_API_VERSION@_la_LIBADD = \
$(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) \
$(JSON_GLIB_CFLAGS) $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) \
$(GST_ALL_LIBS) $(GIO_LIBS) $(GST_PBUTILS_LIBS) \
$(GLIB_LIBS) $(LIBM)
$(JSON_GLIB_LIBS) $(GLIB_LIBS) $(LIBM)
CLEANFILES =

View file

@ -25,6 +25,7 @@
#include <gst/gst.h>
#include "gst-validate-scenario.h"
#include "gst-validate-monitor.h"
#include <json-glib/json-glib.h>
GST_DEBUG_CATEGORY_EXTERN (gstvalidate_debug);
#define GST_CAT_DEFAULT gstvalidate_debug
@ -49,5 +50,6 @@ G_GNUC_INTERNAL void _priv_validate_override_registry_deinit (void);
G_GNUC_INTERNAL GstValidateMonitor * gst_validate_get_monitor (GObject *object);
G_GNUC_INTERNAL void gst_validate_init_runner (void);
G_GNUC_INTERNAL void gst_validate_deinit_runner (void);
G_GNUC_INTERNAL void gst_validate_report_deinit (void);
gboolean gst_validate_send (JsonNode * root);
#endif

View file

@ -90,6 +90,7 @@ print_position (GstValidateMonitor * monitor)
{
GstQuery *query;
gint64 position, duration;
JsonBuilder *jbuilder;
GstElement *pipeline =
GST_ELEMENT (GST_VALIDATE_MONITOR_GET_OBJECT (monitor));
@ -114,6 +115,21 @@ print_position (GstValidateMonitor * monitor)
gst_query_parse_segment (query, &rate, NULL, NULL, NULL);
gst_query_unref (query);
jbuilder = json_builder_new ();
json_builder_begin_object (jbuilder);
json_builder_set_member_name (jbuilder, "type");
json_builder_add_string_value (jbuilder, "position");
json_builder_set_member_name (jbuilder, "position");
json_builder_add_int_value (jbuilder, position);
json_builder_set_member_name (jbuilder, "duration");
json_builder_add_int_value (jbuilder, duration);
json_builder_set_member_name (jbuilder, "speed");
json_builder_add_double_value (jbuilder, rate);
json_builder_end_object (jbuilder);
gst_validate_send (json_builder_get_root (jbuilder));
g_object_unref (jbuilder);
gst_validate_printf (NULL,
"<position: %" GST_TIME_FORMAT " duration: %" GST_TIME_FORMAT
" speed: %f />\r", GST_TIME_ARGS (position), GST_TIME_ARGS (duration),
@ -423,15 +439,21 @@ _bus_handler (GstBus * bus, GstMessage * message,
}
case GST_MESSAGE_BUFFERING:
{
JsonBuilder *jbuilder = json_builder_new ();
GstBufferingMode mode;
gint percent;
gst_message_parse_buffering (message, &percent);
gst_message_parse_buffering_stats (message, &mode, NULL, NULL, NULL);
json_builder_begin_object (jbuilder);
json_builder_set_member_name (jbuilder, "type");
json_builder_add_string_value (jbuilder, "buffering");
json_builder_set_member_name (jbuilder, "state");
if (percent == 100) {
/* a 100% message means buffering is done */
gst_validate_printf (NULL, "\nDone buffering\n");
json_builder_add_string_value (jbuilder, "done");
if (monitor->buffering) {
monitor->print_pos_srcid =
g_timeout_add (PRINT_POSITION_TIMEOUT,
@ -443,13 +465,22 @@ _bus_handler (GstBus * bus, GstMessage * message,
if (!monitor->buffering) {
monitor->buffering = TRUE;
gst_validate_printf (NULL, "\nStart buffering\n");
json_builder_add_string_value (jbuilder, "started");
if (monitor->print_pos_srcid
&& g_source_remove (monitor->print_pos_srcid)) {
monitor->print_pos_srcid = 0;
}
} else {
json_builder_add_string_value (jbuilder, "progress");
}
gst_validate_printf (NULL, "%s %d%% \r", "Buffering...", percent);
}
json_builder_set_member_name (jbuilder, "position");
json_builder_add_int_value (jbuilder, percent);
json_builder_end_object (jbuilder);
gst_validate_send (json_builder_get_root (jbuilder));
g_object_unref (jbuilder);
break;
}
case GST_MESSAGE_STREAM_COLLECTION:

View file

@ -43,8 +43,48 @@ static GstValidateDebugFlags _gst_validate_flags = 0;
static GHashTable *_gst_validate_issues = NULL;
static FILE **log_files = NULL;
GType _gst_validate_report_type;
GST_DEFINE_MINI_OBJECT_TYPE (GstValidateReport, gst_validate_report);
/* Tcp server for communications with gst-validate-launcher */
GSocketClient *socket_client = NULL;
GSocketConnection *server_connection = NULL;
GOutputStream *server_ostream = NULL;
GType _gst_validate_report_type = 0;
static JsonNode *
gst_validate_report_serialize (GstValidateReport * report)
{
JsonNode *node = json_node_alloc ();
JsonObject *jreport = json_object_new ();
json_object_set_string_member (jreport, "type", "report");
json_object_set_string_member (jreport, "summary", report->issue->summary);
json_object_set_string_member (jreport, "level",
gst_validate_report_level_get_name (report->level));
json_object_set_string_member (jreport, "detected-on", report->reporter_name);
json_object_set_string_member (jreport, "details", report->message);
node = json_node_init_object (node, jreport);
return node;
}
GType
gst_validate_report_get_type (void)
{
if (_gst_validate_report_type == 0) {
_gst_validate_report_type =
g_boxed_type_register_static (g_intern_static_string
("GstValidateReport"), (GBoxedCopyFunc) gst_mini_object_ref,
(GBoxedFreeFunc) gst_mini_object_unref);
json_boxed_register_serialize_func (_gst_validate_report_type,
JSON_NODE_OBJECT,
(JsonBoxedSerializeFunc) gst_validate_report_serialize);
}
return _gst_validate_report_type;
}
GRegex *newline_regex = NULL;
@ -319,8 +359,7 @@ gst_validate_report_load_issues (void)
_("a gstreamer plugin is missing and prevented Validate from running"),
NULL);
REGISTER_VALIDATE_ISSUE (CRITICAL, NOT_NEGOTIATED,
_("a NOT NEGOTIATED message has been emitted on the bus."),
NULL);
_("a NOT NEGOTIATED message has been posted on the bus."), NULL);
REGISTER_VALIDATE_ISSUE (WARNING, WARNING_ON_BUS,
_("We got a WARNING message on the bus"), NULL);
REGISTER_VALIDATE_ISSUE (CRITICAL, ERROR_ON_BUS,
@ -348,10 +387,58 @@ gst_validate_report_load_issues (void)
REGISTER_VALIDATE_ISSUE (ISSUE, G_LOG_ISSUE, _("We got a g_log issue"), NULL);
}
gboolean
gst_validate_send (JsonNode * root)
{
gboolean res = FALSE;
JsonGenerator *jgen;
gsize message_length;
gchar *object, *message;
GError *error = NULL;
if (!server_ostream)
goto done;
jgen = json_generator_new ();
json_generator_set_root (jgen, root);
object = json_generator_to_data (jgen, &message_length);
message = g_malloc0 (message_length + 5);
GST_WRITE_UINT32_BE (message, message_length);
strcpy (&message[4], object);
g_free (object);
res = g_output_stream_write_all (server_ostream, message, message_length + 4,
NULL, NULL, &error);
if (!res) {
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PENDING)) {
GST_ERROR ("Stream was busy, trying again later.");
g_free (message);
g_object_unref (jgen);
g_idle_add ((GSourceFunc) gst_validate_send, root);
return G_SOURCE_REMOVE;
}
GST_ERROR ("ERROR: Can't write to remote: %s", error->message);
} else if (!g_output_stream_flush (server_ostream, NULL, &error)) {
GST_ERROR ("ERROR: Can't flush stream: %s", error->message);
}
g_free (message);
g_object_unref (jgen);
done:
json_node_free (root);
return G_SOURCE_REMOVE;
}
void
gst_validate_report_init (void)
{
const gchar *var, *file_env;
const gchar *var, *file_env, *server_env;
const GDebugKey keys[] = {
{"fatal_criticals", GST_VALIDATE_FATAL_CRITICALS},
{"fatal_warnings", GST_VALIDATE_FATAL_WARNINGS},
@ -379,6 +466,42 @@ gst_validate_report_init (void)
gst_validate_report_load_issues ();
}
server_env = g_getenv ("GST_VALIDATE_SERVER");
if (server_env) {
GstUri *server_uri = gst_uri_from_string (server_env);
if (server_uri && !g_strcmp0 (gst_uri_get_scheme (server_uri), "tcp")) {
JsonBuilder *jbuilder;
GError *err = NULL;
socket_client = g_socket_client_new ();
server_connection = g_socket_client_connect_to_host (socket_client,
gst_uri_get_host (server_uri), gst_uri_get_port (server_uri),
NULL, &err);
if (!server_connection) {
g_clear_error (&err);
g_clear_object (&socket_client);
} else {
server_ostream =
g_io_stream_get_output_stream (G_IO_STREAM (server_connection));
jbuilder = json_builder_new ();
json_builder_begin_object (jbuilder);
json_builder_set_member_name (jbuilder, "started");
json_builder_add_boolean_value (jbuilder, TRUE);
json_builder_end_object (jbuilder);
gst_validate_send (json_builder_get_root (jbuilder));
g_object_unref (jbuilder);
}
gst_uri_unref (server_uri);
} else {
GST_ERROR ("Server URI not valid: %s", server_env);
}
}
file_env = g_getenv ("GST_VALIDATE_FILE");
if (file_env != NULL && *file_env != '\0') {
gint i;
@ -391,12 +514,11 @@ gst_validate_report_init (void)
g_malloc0 (sizeof (FILE *) * (g_strv_length (wanted_files) + 1));
for (i = 0; i < g_strv_length (wanted_files); i++) {
FILE *log_file;
if (g_strcmp0 (wanted_files[i], "stderr") == 0) {
log_file = stderr;
} else if (g_strcmp0 (wanted_files[i], "stdout") == 0)
} else if (g_strcmp0 (wanted_files[i], "stdout") == 0) {
log_file = stdout;
else {
} else {
log_file = g_fopen (wanted_files[i], "w");
}
@ -422,6 +544,16 @@ gst_validate_report_init (void)
#endif
}
void
gst_validate_report_deinit (void)
{
if (server_ostream)
g_output_stream_close (server_ostream, NULL, NULL);
g_clear_object (&socket_client);
g_clear_object (&server_connection);
g_clear_object (&server_ostream);
}
GstValidateIssue *
gst_validate_issue_from_id (GstValidateIssueId issue_id)
{

View file

@ -538,6 +538,8 @@ gst_validate_runner_add_report (GstValidateRunner * runner,
GstValidateReportingDetails reporter_level =
gst_validate_reporter_get_reporting_level (report->reporter);
gst_validate_send (json_boxed_serialize (GST_MINI_OBJECT_TYPE (report),
report));
/* Let's use our own reporting strategy */
if (reporter_level == GST_VALIDATE_SHOW_UNKNOWN) {
gst_validate_report_set_reporting_level (report,
@ -633,18 +635,23 @@ _do_report_synthesis (GstValidateRunner * runner)
continue;
report = (GstValidateReport *) (reports->data);
gst_validate_report_print_level (report);
gst_validate_report_print_detected_on (report);
if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL)
if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) {
criticals = g_list_append (criticals, report);
gst_validate_report_print_details (report);
}
for (tmp = g_list_next (reports); tmp; tmp = tmp->next) {
report = (GstValidateReport *) (tmp->data);
gst_validate_report_print_detected_on (report);
if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL)
if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) {
criticals = g_list_append (criticals, report);
gst_validate_report_print_details (report);
}
}
report = (GstValidateReport *) (reports->data);
gst_validate_report_print_description (report);

View file

@ -188,7 +188,7 @@ G_DEFINE_TYPE_WITH_CODE (GstValidateScenario, gst_validate_scenario,
_reporter_iface_init));
/* GstValidateAction implementation */
GType _gst_validate_action_type;
GType _gst_validate_action_type = 0;
struct _GstValidateActionPrivate
{
@ -204,7 +204,42 @@ struct _GstValidateActionPrivate
GWeakRef scenario;
};
GST_DEFINE_MINI_OBJECT_TYPE (GstValidateAction, gst_validate_action);
static JsonNode *
gst_validate_action_serialize (GstValidateAction * action)
{
JsonNode *node = json_node_alloc ();
JsonObject *jreport = json_object_new ();
gchar *action_args = gst_structure_to_string (action->structure);
json_object_set_string_member (jreport, "type", "action");
json_object_set_string_member (jreport, "action-type", action->type);
json_object_set_int_member (jreport, "playback-time",
(gint64) action->playback_time);
json_object_set_string_member (jreport, "args", action_args);
g_free (action_args);
node = json_node_init_object (node, jreport);
return node;
}
GType
gst_validate_action_get_type (void)
{
if (_gst_validate_action_type == 0) {
_gst_validate_action_type =
g_boxed_type_register_static (g_intern_static_string
("GstValidateAction"), (GBoxedCopyFunc) gst_mini_object_ref,
(GBoxedFreeFunc) gst_mini_object_unref);
json_boxed_register_serialize_func (_gst_validate_action_type,
JSON_NODE_OBJECT,
(JsonBoxedSerializeFunc) gst_validate_action_serialize);
}
return _gst_validate_action_type;
}
static GstValidateAction *gst_validate_action_new (GstValidateScenario *
scenario, GstValidateActionType * type);
static gboolean execute_next_action (GstValidateScenario * scenario);
@ -294,6 +329,9 @@ gboolean
_action_check_and_set_printed (GstValidateAction * action)
{
if (action->priv->printed == FALSE) {
gst_validate_send (json_boxed_serialize (GST_MINI_OBJECT_TYPE
(action), action));
action->priv->printed = TRUE;
return FALSE;

View file

@ -46,7 +46,7 @@ gstvalidate = shared_library('gstvalidate',
install: true,
c_args : [gst_c_args],
dependencies : [gst_dep, glib_dep, gio_dep, gmodule_dep,
gst_pbutils_dep, mathlib])
gst_pbutils_dep, mathlib, json_dep])
validate_gen_sources = []
if build_gir

View file

@ -276,6 +276,7 @@ gst_validate_deinit (void)
_priv_validate_override_registry_deinit ();
core_config = NULL;
validate_initialized = FALSE;
gst_validate_report_deinit ();
g_mutex_unlock (&_gst_validate_registry_mutex);
g_mutex_clear (&_gst_validate_registry_mutex);

View file

@ -19,9 +19,12 @@
""" Class representing tests and test managers. """
import json
import os
import sys
import re
import SocketServer
import struct
import time
import utils
import signal
@ -441,6 +444,33 @@ class Test(Loggable):
return self.result
class GstValidateListener(SocketServer.BaseRequestHandler):
def handle(self):
"""Implements BaseRequestHandler handle method"""
while True:
raw_len = self.request.recv(4)
if raw_len == '':
return
msglen = struct.unpack('>I', raw_len)[0]
msg = self.request.recv(msglen)
if msg == '':
return
obj = json.loads(msg)
test = getattr(self.server, "test")
obj_type = obj.get("type", '')
if obj_type == 'position':
test.set_position(obj['position'], obj['duration'],
obj['speed'])
elif obj_type == 'buffering':
test.set_position(obj['position'], 100)
elif obj_type == 'action':
test.add_action_execution(obj)
elif obj_type == 'report':
test.add_report(obj)
class GstValidateTest(Test):
""" A class representing a particular test. """
@ -474,6 +504,11 @@ class GstValidateTest(Test):
if p:
application_name = p
self.reports = []
self.position = -1
self.duration = -1
self.speed = 1.0
self.actions_infos = []
self.media_descriptor = media_descriptor
override_path = self.get_override_file(media_descriptor)
@ -500,6 +535,57 @@ class GstValidateTest(Test):
self.scenario = None
else:
self.scenario = scenario
self.server = None
def stop_server(self):
if self.server:
self.server.server_close()
self.server.shutdown()
self.server_thread.join()
self.server = None
def kill_subprocess(self):
Test.kill_subprocess(self)
self.stop_server()
def add_report(self, report):
self.reports.append(report)
def set_position(self, position, duration, speed=None):
self.position = position
self.duration = duration
if speed:
self.speed = speed
def add_action_execution(self, action_infos):
if action_infos['action-type'] == 'eos':
self._sent_eos_pos = time.time()
self.actions_infos.append(action_infos)
def server_wrapper(self, ready):
self.server = SocketServer.TCPServer(('localhost', 0), GstValidateListener)
self.server.socket.settimeout(0.0)
self.server.test = self
self.serverport = self.server.socket.getsockname()[1]
self.info("%s server port: %s" % (self, self.serverport))
ready.set()
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
self.server.serve_forever()
def test_start(self, queue):
ready = threading.Event()
self.server_thread = threading.Thread(target=self.server_wrapper,
kwargs={'ready': ready})
self.server_thread.start()
ready.wait()
Test.test_start(self, queue)
def test_end(self):
Test.test_end(self)
self.stop_server()
def get_override_file(self, media_descriptor):
if media_descriptor:
@ -510,10 +596,12 @@ class GstValidateTest(Test):
return None
def get_current_position(self):
return self.position
def get_current_value(self):
if self.scenario:
sent_eos = self.sent_eos_position()
if sent_eos is not None:
if self._sent_eos_pos is not None:
t = time.time()
if ((t - sent_eos)) > 30:
if self.media_descriptor.get_protocol() == Protocols.HLS:
@ -528,7 +616,7 @@ class GstValidateTest(Test):
return Result.FAILED
return self.get_current_position()
return self.position
def get_subproc_env(self):
self.validatelogs = self.logfile + '.validate.logs'
@ -541,6 +629,7 @@ class GstValidateTest(Test):
utils.touch(self.validatelogs)
subproc_env["GST_VALIDATE_FILE"] = logfiles
subproc_env["GST_VALIDATE_SERVER"] = "tcp://localhost:%s" % self.serverport
self.extra_logfiles.append(self.validatelogs)
if 'GST_DEBUG' in os.environ and not self.options.redirect_logs:
@ -598,21 +687,15 @@ class GstValidateTest(Test):
return value
def get_validate_criticals_errors(self):
ret = "["
errors = []
for l in open(self.validatelogs, 'r').readlines():
if "critical : " in l:
error = l.split("critical : ")[1].replace("\n", '')
if error not in errors:
if ret != "[":
ret += ", "
ret += error
errors.append(error)
ret = []
for report in self.reports:
if report['level'] == 'critical':
ret.append(report['summary'])
if ret == "[":
if not ret:
return None
else:
return ret + "]"
return str(ret)
def check_results(self):
if self.result is Result.FAILED or self.result is Result.PASSED or self.result is Result.TIMEOUT:
@ -638,89 +721,6 @@ class GstValidateTest(Test):
else:
self.set_result(Result.PASSED)
def _parse_position(self, p):
self.log("Parsing %s" % p)
times = self.findpos_regex.findall(p)
if len(times) != 1:
self.warning("Got a unparsable value: %s" % p)
return 0, 0
return (utils.gsttime_from_tuple(times[0][:4]),
utils.gsttime_from_tuple(times[0][4:]))
def _parse_buffering(self, b):
return b.lower().split("buffering... ")[1].split("%")[0], 100
def _get_position(self):
position = duration = -1
self.debug("Getting position")
m = None
for l in reversed(open(self.validatelogs, 'r').readlines()):
l = l.lower()
if "<position:" in l or "buffering" in l:
m = l
break
if m is None:
self.debug("Could not fine any positionning info")
return position, duration
for j in m.split("\r"):
j = j.lstrip().rstrip()
if j.startswith("<position:") and j.endswith("/>"):
position, duration = self._parse_position(j)
elif j.startswith("buffering") and j.endswith("%"):
position, duration = self._parse_buffering(j)
else:
self.log("No info in %s" % j)
return position, duration
def _get_last_seek_values(self):
m = None
rate = start = stop = None
for l in reversed(open(self.validatelogs, 'r').readlines()):
l = l.lower()
if "seeking to: " in l:
m = l
break
if m is None:
self.debug("Could not fine any seeking info")
return start, stop, rate
values = self.findlastseek_regex.findall(m)
if len(values) != 1:
self.warning("Got an unparsable seek value %s", m)
return start, stop, rate
v = values[0]
return (utils.gsttime_from_tuple(v[:4]),
utils.gsttime_from_tuple(v[4:8]),
float(str(v[8]) + "." + str(v[9])))
def sent_eos_position(self):
if self._sent_eos_pos is not None:
return self._sent_eos_pos
for l in reversed(open(self.validatelogs, 'r').readlines()):
l = l.lower()
if "sending eos" in l:
self._sent_eos_pos = time.time()
return self._sent_eos_pos
return None
def get_current_position(self):
position, duration = self._get_position()
if position == -1:
return position
return position
def get_valgrind_suppression_file(self, subdir, name):
p = get_data_file(subdir, name)
if p:

View file

@ -1,5 +1,6 @@
inc_dirs = include_directories('.')
json_dep = dependency('json-glib-1.0')
cdata = configuration_data()
cdata.set('GST_API_VERSION', '"@0@"'.format(apiversion))
cdata.set('VALIDATEPLUGINDIR', '"@0@/@1@/gstreamer-1.0/validate"'.format(get_option('prefix'),get_option('libdir')))