From: Johan Lorensson Date: Tue, 26 Sep 2017 06:33:40 +0000 (+0200) Subject: Merge pull request #5433 from lateralusX/lateralusX/windows-mono-debugger-soft-hang X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=commitdiff_plain;h=f0cb764a7eaf46ef5c71feab8d505091fd257b42;hp=1df9fdf61349296ce621e3ef73d5cc594cdbb539;p=mono.git Merge pull request #5433 from lateralusX/lateralusX/windows-mono-debugger-soft-hang Fix sporadic hang in Mono.Debugger.Soft test suite on Windows. --- diff --git a/Makefile.am b/Makefile.am index c322d249127..12b27f14b08 100644 --- a/Makefile.am +++ b/Makefile.am @@ -145,4 +145,7 @@ update-llvm-version: update-solution-files: make update-csproj make package-inputs - (cd msvc/scripts; make genproj.exe; mono genproj.exe) + (cd msvc/scripts; make genproj.exe; mono genproj.exe $(GENPROJ_ARGS)) + +update-solution-files-with-tests: + make "GENPROJ_ARGS=2012 true true" update-solution-files diff --git a/man/mono.1 b/man/mono.1 index 7d2f14c151e..5ac6ea81002 100644 --- a/man/mono.1 +++ b/man/mono.1 @@ -908,8 +908,11 @@ your profiler. For a sample of how to write your own custom profiler look in the Mono source tree for in the samples/profiler.c. .SH CODE COVERAGE -Mono ships with a code coverage module in the \f[I]log\f[] profiler. -Check the `coverage' option on the mprof-report(1) page for more details. +Mono ships with a code coverage module in the \f[I]coverage\f[] profiler. +To enable it, pass \fB--profile=coverage\fR to your mono invocation. It +will by default output a coverage.xml in the current directory. Use +\fBmono --profile=coverage:help sample.exe\fR for more information on the +different options. .SH AOT PROFILING You can improve startup performance by using the AOT profiler. .PP diff --git a/man/mprof-report.1 b/man/mprof-report.1 index 56663251bf8..45ea4f77b88 100644 --- a/man/mprof-report.1 +++ b/man/mprof-report.1 @@ -213,9 +213,6 @@ The following commands are available: .IP \[bu] 2 \f[I]nocounters\f[]: disables sampling of runtime and performance counters, which is normally done every 1 second. -.IP \[bu] 2 -\f[I]coverage\f[]: collect code coverage data. This implies enabling -the \f[I]calls\f[] option. .RE .SS Analyzing the profile data .PP @@ -338,8 +335,6 @@ version .IP \[bu] 2 \f[I]counters\f[]: counters samples .IP \[bu] 2 -\f[I]coverage\f[]: code coverage data -.IP \[bu] 2 \f[I]stats\f[]: event statistics .PP It is possible to limit some of the data displayed to a timeframe @@ -411,13 +406,6 @@ By default mprof-report will print the summary data to the console. To print it to a file, instead, use the option: .PP \f[B]--out=FILENAME\f[] -.SS Processing code coverage data -.PP -If you ran the profiler with the \f[I]coverage\f[] option, you can -process the collected coverage data into an XML file by running -mprof-report like this: -.PP -\f[B]mprof-report --coverage-out=coverage.xml output.mlpd\f[] .SS Dealing with profiler slowness .PP If the profiler needs to collect lots of data, the execution of the diff --git a/mono/arch/ppc/ppc-codegen.h b/mono/arch/ppc/ppc-codegen.h index 869365b4bcc..98fed7525fb 100644 --- a/mono/arch/ppc/ppc-codegen.h +++ b/mono/arch/ppc/ppc-codegen.h @@ -333,7 +333,7 @@ my and Ximian's copyright to this code. ;) #define ppc_andid(c,S,A,ui) ppc_emit32(c, (28 << 26) | ((S) << 21 ) | ((A) << 16) | ((guint16)(ui))) #define ppc_andisd(c,S,A,ui) ppc_emit32(c, (29 << 26) | ((S) << 21 ) | ((A) << 16) | ((guint16)(ui))) -#define ppc_bcx(c,BO,BI,BD,AA,LK) ppc_emit32(c, (16 << 26) | (BO << 21 )| (BI << 16) | (BD << 2) | ((AA) << 1) | LK) +#define ppc_bcx(c,BO,BI,BD,AA,LK) ppc_emit32(c, (16 << 26) | ((BO) << 21 )| ((BI) << 16) | (BD << 2) | ((AA) << 1) | LK) #define ppc_bc(c,BO,BI,BD) ppc_bcx(c,BO,BI,BD,0,0) #define ppc_bca(c,BO,BI,BD) ppc_bcx(c,BO,BI,BD,1,0) #define ppc_bcl(c,BO,BI,BD) ppc_bcx(c,BO,BI,BD,0,1) diff --git a/mono/metadata/class.c b/mono/metadata/class.c index f00d436281a..f3e87fe6428 100644 --- a/mono/metadata/class.c +++ b/mono/metadata/class.c @@ -2926,6 +2926,10 @@ collect_implemented_interfaces_aux (MonoClass *klass, GPtrArray **res, GHashTabl *ifaces = g_hash_table_new (NULL, NULL); if (g_hash_table_lookup (*ifaces, ic)) continue; + /* A gparam is not an implemented interface for the purposes of + * mono_class_get_implemented_interfaces */ + if (mono_class_is_gparam (ic)) + continue; g_ptr_array_add (*res, ic); g_hash_table_insert (*ifaces, ic, ic); mono_class_init (ic); @@ -3316,7 +3320,9 @@ setup_interface_offsets (MonoClass *klass, int cur_slot, gboolean overwrite) for (i = 0; i < k->interface_count; i++) { ic = k->interfaces [i]; - mono_class_init (ic); + /* A gparam does not have any interface_id set. */ + if (! mono_class_is_gparam (ic)) + mono_class_init (ic); if (max_iid < ic->interface_id) max_iid = ic->interface_id; diff --git a/mono/metadata/class.h b/mono/metadata/class.h index f79cd9bff99..565dac59e4f 100644 --- a/mono/metadata/class.h +++ b/mono/metadata/class.h @@ -45,6 +45,7 @@ mono_class_from_typeref (MonoImage *image, uint32_t type_token); MONO_API MonoClass * mono_class_from_typeref_checked (MonoImage *image, uint32_t type_token, MonoError *error); +MONO_RT_EXTERNAL_ONLY MONO_API MonoClass * mono_class_from_generic_parameter (MonoGenericParam *param, MonoImage *image, mono_bool is_mvar); diff --git a/mono/metadata/w32handle.c b/mono/metadata/w32handle.c index 98d4ace6d62..654b215538d 100644 --- a/mono/metadata/w32handle.c +++ b/mono/metadata/w32handle.c @@ -1218,8 +1218,13 @@ mono_w32handle_wait_multiple (gpointer *handles, gsize nhandles, gboolean waital signalled = (waitall && count == nhandles) || (!waitall && count > 0); if (signalled) { - for (i = 0; i < nhandles; i++) - own_if_signalled (handles [i], &abandoned [i]); + for (i = 0; i < nhandles; i++) { + if (own_if_signalled (handles [i], &abandoned [i]) && !waitall) { + /* if we are calling WaitHandle.WaitAny, .NET only owns the first one; it matters for Mutex which + * throw AbandonedMutexException in case we owned it but didn't release it */ + break; + } + } } mono_w32handle_unlock_handles (handles, nhandles); diff --git a/mono/mini/aot-compiler.c b/mono/mini/aot-compiler.c index 125110303ad..6b91543a56b 100644 --- a/mono/mini/aot-compiler.c +++ b/mono/mini/aot-compiler.c @@ -2897,7 +2897,7 @@ encode_klass_ref_inner (MonoAotCompile *acfg, MonoClass *klass, guint8 *buf, gui if (par->gshared_constraint) { MonoGSharedGenericParam *gpar = (MonoGSharedGenericParam*)par; encode_type (acfg, par->gshared_constraint, p, &p); - encode_klass_ref (acfg, mono_class_from_generic_parameter (gpar->parent, NULL, klass->byval_arg.type == MONO_TYPE_MVAR), p, &p); + encode_klass_ref (acfg, mono_class_from_generic_parameter_internal (gpar->parent), p, &p); } else { encode_value (klass->byval_arg.type, p, &p); encode_value (mono_type_get_generic_param_num (&klass->byval_arg), p, &p); diff --git a/mono/mini/cpu-ppc.md b/mono/mini/cpu-ppc.md index 65cdc9b149d..0baa0fe3c99 100644 --- a/mono/mini/cpu-ppc.md +++ b/mono/mini/cpu-ppc.md @@ -198,6 +198,9 @@ float_cgt_un: dest:i src1:f src2:f len:20 float_clt: dest:i src1:f src2:f len:16 float_clt_un: dest:i src1:f src2:f len:20 float_conv_to_u: dest:i src1:f len:36 +float_cneq: dest:i src1:f src2:f len:16 +float_cge: dest:i src1:f src2:f len:16 +float_cle: dest:i src1:f src2:f len:16 call_handler: len:12 clob:c endfilter: src1:i len:32 aot_const: dest:i len:8 @@ -289,6 +292,12 @@ int_cgt_un: dest:i len:12 int_clt: dest:i len:12 int_clt_un: dest:i len:12 +int_cneq: dest:i len:12 +int_cge: dest:i len:12 +int_cle: dest:i len:12 +int_cge_un: dest:i len:12 +int_cle_un: dest:i len:12 + cond_exc_ieq: len:8 cond_exc_ine_un: len:8 cond_exc_ilt: len:8 diff --git a/mono/mini/cpu-ppc64.md b/mono/mini/cpu-ppc64.md index 31119afdafc..2c28e7dca7c 100644 --- a/mono/mini/cpu-ppc64.md +++ b/mono/mini/cpu-ppc64.md @@ -201,6 +201,9 @@ float_cgt_un: dest:i src1:f src2:f len:20 float_clt: dest:i src1:f src2:f len:16 float_clt_un: dest:i src1:f src2:f len:20 float_conv_to_u: dest:i src1:f len:36 +float_cneq: dest:i src1:f src2:f len:16 +float_cge: dest:i src1:f src2:f len:16 +float_cle: dest:i src1:f src2:f len:16 call_handler: len:12 clob:c endfilter: src1:i len:20 aot_const: dest:i len:8 @@ -293,6 +296,12 @@ int_cgt_un: dest:i len:12 int_clt: dest:i len:12 int_clt_un: dest:i len:12 +int_cneq: dest:i len:12 +int_cge: dest:i len:12 +int_cle: dest:i len:12 +int_cge_un: dest:i len:12 +int_cle_un: dest:i len:12 + cond_exc_ieq: len:8 cond_exc_ine_un: len:8 cond_exc_ilt: len:8 diff --git a/mono/mini/mini-ppc.c b/mono/mini/mini-ppc.c index 1a947639a24..4ba20cc2257 100644 --- a/mono/mini/mini-ppc.c +++ b/mono/mini/mini-ppc.c @@ -1005,8 +1005,7 @@ get_call_info (MonoMethodSignature *sig) fr = PPC_FIRST_FPARG_REG; gr = PPC_FIRST_ARG_REG; - /* FIXME: handle returning a struct */ - if (MONO_TYPE_ISSTRUCT (sig->ret)) { + if (mini_type_is_vtype (sig->ret)) { cinfo->vtype_retaddr = TRUE; } @@ -4126,6 +4125,11 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) ppc_mtctr (code, ins->sreg1); ppc_bcctr (code, PPC_BR_ALWAYS, 0); break; + case OP_ICNEQ: + ppc_li (code, ins->dreg, 0); + ppc_bc (code, PPC_BR_TRUE, PPC_BR_EQ, 2); + ppc_li (code, ins->dreg, 1); + break; case OP_CEQ: case OP_ICEQ: CASE_PPC64 (OP_LCEQ) @@ -4143,6 +4147,12 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) ppc_bc (code, PPC_BR_TRUE, PPC_BR_LT, 2); ppc_li (code, ins->dreg, 0); break; + case OP_ICGE: + case OP_ICGE_UN: + ppc_li (code, ins->dreg, 1); + ppc_bc (code, PPC_BR_FALSE, PPC_BR_LT, 2); + ppc_li (code, ins->dreg, 0); + break; case OP_CGT: case OP_CGT_UN: case OP_ICGT: @@ -4153,6 +4163,12 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) ppc_bc (code, PPC_BR_TRUE, PPC_BR_GT, 2); ppc_li (code, ins->dreg, 0); break; + case OP_ICLE: + case OP_ICLE_UN: + ppc_li (code, ins->dreg, 1); + ppc_bc (code, PPC_BR_FALSE, PPC_BR_GT, 2); + ppc_li (code, ins->dreg, 0); + break; case OP_COND_EXC_EQ: case OP_COND_EXC_NE_UN: case OP_COND_EXC_LT: @@ -4355,15 +4371,17 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) ppc_fcmpu (code, 0, ins->sreg1, ins->sreg2); break; case OP_FCEQ: + case OP_FCNEQ: ppc_fcmpo (code, 0, ins->sreg1, ins->sreg2); - ppc_li (code, ins->dreg, 0); - ppc_bc (code, PPC_BR_FALSE, PPC_BR_EQ, 2); ppc_li (code, ins->dreg, 1); + ppc_bc (code, ins->opcode == OP_FCEQ ? PPC_BR_TRUE : PPC_BR_FALSE, PPC_BR_EQ, 2); + ppc_li (code, ins->dreg, 0); break; case OP_FCLT: + case OP_FCGE: ppc_fcmpo (code, 0, ins->sreg1, ins->sreg2); ppc_li (code, ins->dreg, 1); - ppc_bc (code, PPC_BR_TRUE, PPC_BR_LT, 2); + ppc_bc (code, ins->opcode == OP_FCLT ? PPC_BR_TRUE : PPC_BR_FALSE, PPC_BR_LT, 2); ppc_li (code, ins->dreg, 0); break; case OP_FCLT_UN: @@ -4374,9 +4392,10 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) ppc_li (code, ins->dreg, 0); break; case OP_FCGT: + case OP_FCLE: ppc_fcmpo (code, 0, ins->sreg1, ins->sreg2); ppc_li (code, ins->dreg, 1); - ppc_bc (code, PPC_BR_TRUE, PPC_BR_GT, 2); + ppc_bc (code, ins->opcode == OP_FCGT ? PPC_BR_TRUE : PPC_BR_FALSE, PPC_BR_GT, 2); ppc_li (code, ins->dreg, 0); break; case OP_FCGT_UN: diff --git a/mono/profiler/Makefile.am b/mono/profiler/Makefile.am index 88cd9d37a1a..212278b57e4 100644 --- a/mono/profiler/Makefile.am +++ b/mono/profiler/Makefile.am @@ -25,6 +25,8 @@ lib_LTLIBRARIES = \ libmono-profiler-iomap-static.la \ libmono-profiler-log.la \ libmono-profiler-log-static.la \ + libmono-profiler-coverage.la \ + libmono-profiler-coverage-static.la \ $(vtune_libs) suppressiondir = $(datadir)/mono-$(API_VER)/mono/profiler @@ -83,6 +85,12 @@ libmono_profiler_log_la_LDFLAGS = $(prof_ldflags) libmono_profiler_log_static_la_SOURCES = log.c log-args.c libmono_profiler_log_static_la_LDFLAGS = -static +libmono_profiler_coverage_la_SOURCES = coverage.c +libmono_profiler_coverage_la_LIBADD = $(libmono_dep) $(GLIB_LIBS) $(Z_LIBS) +libmono_profiler_coverage_la_LDFLAGS = $(prof_ldflags) +libmono_profiler_coverage_static_la_SOURCES = coverage.c +libmono_profiler_coverage_static_la_LDFLAGS = -static + if HAVE_VTUNE libmono_profiler_vtune_la_SOURCES = vtune.c libmono_profiler_vtune_la_CFLAGS = $(VTUNE_CFLAGS) diff --git a/mono/profiler/coverage.c b/mono/profiler/coverage.c new file mode 100644 index 00000000000..5a9d5b69c4f --- /dev/null +++ b/mono/profiler/coverage.c @@ -0,0 +1,923 @@ +/* + * coverage.c: mono coverage profiler + * + * Authors: + * Paolo Molaro (lupus@ximian.com) + * Alex Rønne Petersen (alexrp@xamarin.com) + * Ludovic Henry (ludovic@xamarin.com) + * + * Licensed under the MIT license. See LICENSE file in the project root for full license information. + */ + +/* + * The Coverage XML output schema + * + * + * + * + * + * + * + * + * Elements: + * - The root element of the documentation. It can contain any number of + * , or elements. + * Attributes: + * - version: The version number for the file format - (eg: "0.3") + * - Contains data about assemblies. Has no child elements + * Attributes: + * - name: The name of the assembly - (eg: "System.Xml") + * - guid: The GUID of the assembly + * - filename: The filename of the assembly + * - method-count: The number of methods in the assembly + * - full: The number of fully covered methods + * - partial: The number of partially covered methods + * - Contains data about classes. Has no child elements + * Attributes: + * - name: The name of the class + * - method-count: The number of methods in the class + * - full: The number of fully covered methods + * - partial: The number of partially covered methods + * - Contains data about methods. Can contain any number of elements + * Attributes: + * - assembly: The name of the parent assembly + * - class: The name of the parent class + * - name: The name of the method, with all it's parameters + * - filename: The name of the source file containing this method + * - token + * - Contains data about IL statements. Has no child elements + * Attributes: + * - offset: The offset of the statement in the IL code after the previous + * statement's offset + * - counter: 1 if the line was covered, 0 if it was not + * - line: The line number in the parent method's file + * - column: The column on the line + */ + +#include +#include + +#include + +#ifdef HAVE_DLFCN_H +#include +#endif +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_SYS_MMAN_H +#include +#endif +#if defined (HAVE_SYS_ZLIB) +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +// Statistics for profiler events. +static gint32 coverage_methods_ctr, + coverage_statements_ctr, + coverage_classes_ctr, + coverage_assemblies_ctr; + +struct _MonoProfiler { + MonoProfilerHandle handle; + + FILE* file; + + char *args; + + mono_mutex_t mutex; + GPtrArray *data; + + GPtrArray *filters; + MonoConcurrentHashTable *filtered_classes; + MonoConcurrentHashTable *suppressed_assemblies; + + MonoConcurrentHashTable *methods; + MonoConcurrentHashTable *assemblies; + MonoConcurrentHashTable *classes; + + MonoConcurrentHashTable *image_to_methods; + + guint32 previous_offset; +}; + +typedef struct { + //Where to compress the output file + gboolean use_zip; + + //Name of the generated xml file + const char *output_filename; + + //Filter files used by the code coverage mode + GPtrArray *cov_filter_files; +} ProfilerConfig; + +static ProfilerConfig coverage_config; +static struct _MonoProfiler coverage_profiler; + +/* This is a very basic escape function that escapes < > and & + Ideally we'd use g_markup_escape_string but that function isn't + available in Mono's eglib. This was written without looking at the + source of that function in glib. */ +static char * +escape_string_for_xml (const char *string) +{ + GString *string_builder = g_string_new (NULL); + const char *start, *p; + + start = p = string; + while (*p) { + while (*p && *p != '&' && *p != '<' && *p != '>') + p++; + + g_string_append_len (string_builder, start, p - start); + + if (*p == '\0') + break; + + switch (*p) { + case '<': + g_string_append (string_builder, "<"); + break; + + case '>': + g_string_append (string_builder, ">"); + break; + + case '&': + g_string_append (string_builder, "&"); + break; + + default: + break; + } + + p++; + start = p; + } + + return g_string_free (string_builder, FALSE); +} + +typedef struct { + MonoLockFreeQueueNode node; + MonoMethod *method; +} MethodNode; + +typedef struct { + int offset; + int counter; + char *filename; + int line; + int column; +} CoverageEntry; + +static void +free_coverage_entry (gpointer data, gpointer userdata) +{ + CoverageEntry *entry = (CoverageEntry *)data; + g_free (entry->filename); + g_free (entry); +} + +static void +obtain_coverage_for_method (MonoProfiler *prof, const MonoProfilerCoverageData *entry) +{ + g_assert (prof == &coverage_profiler); + + int offset = entry->il_offset - coverage_profiler.previous_offset; + CoverageEntry *e = g_new (CoverageEntry, 1); + + coverage_profiler.previous_offset = entry->il_offset; + + e->offset = offset; + e->counter = entry->counter; + e->filename = g_strdup(entry->file_name ? entry->file_name : ""); + e->line = entry->line; + e->column = entry->column; + + g_ptr_array_add (coverage_profiler.data, e); +} + +static char * +parse_generic_type_names(char *name) +{ + char *new_name, *ret; + int within_generic_declaration = 0, generic_members = 1; + + if (name == NULL || *name == '\0') + return g_strdup (""); + + if (!(ret = new_name = (char *) g_calloc (strlen (name) * 4 + 1, sizeof (char)))) + return NULL; + + do { + switch (*name) { + case '<': + within_generic_declaration = 1; + break; + + case '>': + within_generic_declaration = 0; + + if (*(name - 1) != '<') { + *new_name++ = '`'; + *new_name++ = '0' + generic_members; + } else { + memcpy (new_name, "<>", 8); + new_name += 8; + } + + generic_members = 0; + break; + + case ',': + generic_members++; + break; + + default: + if (!within_generic_declaration) + *new_name++ = *name; + + break; + } + } while (*name++); + + return ret; +} + +static void +dump_method (gpointer key, gpointer value, gpointer userdata) +{ + MonoMethod *method = (MonoMethod *)value; + MonoClass *klass; + MonoImage *image; + char *class_name, *escaped_image_name, *escaped_class_name, *escaped_method_name, *escaped_method_signature, *escaped_method_filename; + const char *image_name, *method_name, *method_signature, *method_filename; + guint i; + + coverage_profiler.previous_offset = 0; + coverage_profiler.data = g_ptr_array_new (); + + mono_profiler_get_coverage_data (coverage_profiler.handle, method, obtain_coverage_for_method); + + klass = mono_method_get_class (method); + image = mono_class_get_image (klass); + image_name = mono_image_get_name (image); + + method_signature = mono_signature_get_desc (mono_method_signature (method), TRUE); + class_name = parse_generic_type_names (mono_type_get_name (mono_class_get_type (klass))); + method_name = mono_method_get_name (method); + + if (coverage_profiler.data->len != 0) { + CoverageEntry *entry = (CoverageEntry *)coverage_profiler.data->pdata[0]; + method_filename = entry->filename ? entry->filename : ""; + } else + method_filename = ""; + + image_name = image_name ? image_name : ""; + method_signature = method_signature ? method_signature : ""; + method_name = method_name ? method_name : ""; + + escaped_image_name = escape_string_for_xml (image_name); + escaped_class_name = escape_string_for_xml (class_name); + escaped_method_name = escape_string_for_xml (method_name); + escaped_method_signature = escape_string_for_xml (method_signature); + escaped_method_filename = escape_string_for_xml (method_filename); + + fprintf (coverage_profiler.file, "\t\n", + escaped_image_name, escaped_class_name, escaped_method_name, escaped_method_signature, escaped_method_filename, mono_method_get_token (method)); + + g_free (escaped_image_name); + g_free (escaped_class_name); + g_free (escaped_method_name); + g_free (escaped_method_signature); + g_free (escaped_method_filename); + + for (i = 0; i < coverage_profiler.data->len; i++) { + CoverageEntry *entry = (CoverageEntry *)coverage_profiler.data->pdata[i]; + + fprintf (coverage_profiler.file, "\t\t\n", + entry->offset, entry->counter, entry->line, entry->column); + } + + fprintf (coverage_profiler.file, "\t\n"); + + g_free (class_name); + + g_ptr_array_foreach (coverage_profiler.data, free_coverage_entry, NULL); + g_ptr_array_free (coverage_profiler.data, TRUE); +} + +/* This empties the queue */ +static guint +count_queue (MonoLockFreeQueue *queue) +{ + MonoLockFreeQueueNode *node; + guint count = 0; + + while ((node = mono_lock_free_queue_dequeue (queue))) { + count++; + mono_thread_hazardous_try_free (node, g_free); + } + + return count; +} + +static void +dump_classes_for_image (gpointer key, gpointer value, gpointer userdata) +{ + MonoClass *klass = (MonoClass *)key; + MonoLockFreeQueue *class_methods = (MonoLockFreeQueue *)value; + MonoImage *image; + char *class_name, *escaped_class_name; + const char *image_name; + int number_of_methods, partially_covered; + guint fully_covered; + + image = mono_class_get_image (klass); + image_name = mono_image_get_name (image); + + if (!image_name || strcmp (image_name, mono_image_get_name (((MonoImage*) userdata))) != 0) + return; + + class_name = mono_type_get_name (mono_class_get_type (klass)); + + number_of_methods = mono_class_num_methods (klass); + fully_covered = count_queue (class_methods); + /* We don't handle partial covered yet */ + partially_covered = 0; + + escaped_class_name = escape_string_for_xml (class_name); + + fprintf (coverage_profiler.file, "\t\n", + escaped_class_name, number_of_methods, fully_covered, partially_covered); + + g_free (escaped_class_name); + + g_free (class_name); + +} + +static void +get_coverage_for_image (MonoImage *image, int *number_of_methods, guint *fully_covered, int *partially_covered) +{ + MonoLockFreeQueue *image_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.image_to_methods, image); + + *number_of_methods = mono_image_get_table_rows (image, MONO_TABLE_METHOD); + if (image_methods) + *fully_covered = count_queue (image_methods); + else + *fully_covered = 0; + + // FIXME: We don't handle partially covered yet. + *partially_covered = 0; +} + +static void +dump_assembly (gpointer key, gpointer value, gpointer userdata) +{ + MonoAssembly *assembly = (MonoAssembly *)value; + MonoImage *image = mono_assembly_get_image (assembly); + const char *image_name, *image_guid, *image_filename; + char *escaped_image_name, *escaped_image_filename; + int number_of_methods = 0, partially_covered = 0; + guint fully_covered = 0; + + image_name = mono_image_get_name (image); + image_guid = mono_image_get_guid (image); + image_filename = mono_image_get_filename (image); + + image_name = image_name ? image_name : ""; + image_guid = image_guid ? image_guid : ""; + image_filename = image_filename ? image_filename : ""; + + get_coverage_for_image (image, &number_of_methods, &fully_covered, &partially_covered); + + escaped_image_name = escape_string_for_xml (image_name); + escaped_image_filename = escape_string_for_xml (image_filename); + + fprintf (coverage_profiler.file, "\t\n", + escaped_image_name, image_guid, escaped_image_filename, number_of_methods, fully_covered, partially_covered); + + g_free (escaped_image_name); + g_free (escaped_image_filename); + + mono_conc_hashtable_foreach (coverage_profiler.classes, dump_classes_for_image, image); +} + +static void +dump_coverage (void) +{ + fprintf (coverage_profiler.file, "\n"); + fprintf (coverage_profiler.file, "\n"); + + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_foreach (coverage_profiler.assemblies, dump_assembly, NULL); + mono_conc_hashtable_foreach (coverage_profiler.methods, dump_method, NULL); + mono_os_mutex_unlock (&coverage_profiler.mutex); + + fprintf (coverage_profiler.file, "\n"); +} + +static MonoLockFreeQueueNode * +create_method_node (MonoMethod *method) +{ + MethodNode *node = (MethodNode *) g_malloc (sizeof (MethodNode)); + mono_lock_free_queue_node_init ((MonoLockFreeQueueNode *) node, FALSE); + node->method = method; + + return (MonoLockFreeQueueNode *) node; +} + +static gboolean +coverage_filter (MonoProfiler *prof, MonoMethod *method) +{ + MonoError error; + MonoClass *klass; + MonoImage *image; + MonoAssembly *assembly; + MonoMethodHeader *header; + guint32 iflags, flags, code_size; + char *fqn, *classname; + gboolean has_positive, found; + MonoLockFreeQueue *image_methods, *class_methods; + MonoLockFreeQueueNode *node; + + g_assert (prof == &coverage_profiler); + + flags = mono_method_get_flags (method, &iflags); + if ((iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) || + (flags & METHOD_ATTRIBUTE_PINVOKE_IMPL)) + return FALSE; + + // Don't need to do anything else if we're already tracking this method + if (mono_conc_hashtable_lookup (coverage_profiler.methods, method)) + return TRUE; + + klass = mono_method_get_class (method); + image = mono_class_get_image (klass); + + // Don't handle coverage for the core assemblies + if (mono_conc_hashtable_lookup (coverage_profiler.suppressed_assemblies, (gpointer) mono_image_get_name (image)) != NULL) + return FALSE; + + if (coverage_profiler.filters) { + /* Check already filtered classes first */ + if (mono_conc_hashtable_lookup (coverage_profiler.filtered_classes, klass)) + return FALSE; + + classname = mono_type_get_name (mono_class_get_type (klass)); + + fqn = g_strdup_printf ("[%s]%s", mono_image_get_name (image), classname); + + // Check positive filters first + has_positive = FALSE; + found = FALSE; + for (guint i = 0; i < coverage_profiler.filters->len; ++i) { + char *filter = (char *)g_ptr_array_index (coverage_profiler.filters, i); + + if (filter [0] == '+') { + filter = &filter [1]; + + if (strstr (fqn, filter) != NULL) + found = TRUE; + + has_positive = TRUE; + } + } + + if (has_positive && !found) { + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_insert (coverage_profiler.filtered_classes, klass, klass); + mono_os_mutex_unlock (&coverage_profiler.mutex); + g_free (fqn); + g_free (classname); + + return FALSE; + } + + for (guint i = 0; i < coverage_profiler.filters->len; ++i) { + // FIXME: Is substring search sufficient? + char *filter = (char *)g_ptr_array_index (coverage_profiler.filters, i); + if (filter [0] == '+') + continue; + + // Skip '-' + filter = &filter [1]; + + if (strstr (fqn, filter) != NULL) { + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_insert (coverage_profiler.filtered_classes, klass, klass); + mono_os_mutex_unlock (&coverage_profiler.mutex); + g_free (fqn); + g_free (classname); + + return FALSE; + } + } + + g_free (fqn); + g_free (classname); + } + + header = mono_method_get_header_checked (method, &error); + mono_error_cleanup (&error); + + mono_method_header_get_code (header, &code_size, NULL); + + assembly = mono_image_get_assembly (image); + + // Need to keep the assemblies around for as long as they are kept in the hashtable + // Nunit, for example, has a habit of unloading them before the coverage statistics are + // generated causing a crash. See https://bugzilla.xamarin.com/show_bug.cgi?id=39325 + mono_assembly_addref (assembly); + + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_insert (coverage_profiler.methods, method, method); + mono_conc_hashtable_insert (coverage_profiler.assemblies, assembly, assembly); + mono_os_mutex_unlock (&coverage_profiler.mutex); + + image_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.image_to_methods, image); + + if (image_methods == NULL) { + image_methods = (MonoLockFreeQueue *) g_malloc (sizeof (MonoLockFreeQueue)); + mono_lock_free_queue_init (image_methods); + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_insert (coverage_profiler.image_to_methods, image, image_methods); + mono_os_mutex_unlock (&coverage_profiler.mutex); + } + + node = create_method_node (method); + mono_lock_free_queue_enqueue (image_methods, node); + + class_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.classes, klass); + + if (class_methods == NULL) { + class_methods = (MonoLockFreeQueue *) g_malloc (sizeof (MonoLockFreeQueue)); + mono_lock_free_queue_init (class_methods); + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_insert (coverage_profiler.classes, klass, class_methods); + mono_os_mutex_unlock (&coverage_profiler.mutex); + } + + node = create_method_node (method); + mono_lock_free_queue_enqueue (class_methods, node); + + return TRUE; +} + +#define LINE_BUFFER_SIZE 4096 +/* Max file limit of 128KB */ +#define MAX_FILE_SIZE 128 * 1024 +static char * +get_file_content (const gchar *filename) +{ + char *buffer; + ssize_t bytes_read; + long filesize; + int res, offset = 0; + FILE *stream; + + stream = fopen (filename, "r"); + if (stream == NULL) + return NULL; + + res = fseek (stream, 0, SEEK_END); + if (res < 0) { + fclose (stream); + return NULL; + } + + filesize = ftell (stream); + if (filesize < 0) { + fclose (stream); + return NULL; + } + + res = fseek (stream, 0, SEEK_SET); + if (res < 0) { + fclose (stream); + return NULL; + } + + if (filesize > MAX_FILE_SIZE) { + fclose (stream); + return NULL; + } + + buffer = (char *) g_malloc ((filesize + 1) * sizeof (char)); + while ((bytes_read = fread (buffer + offset, 1, LINE_BUFFER_SIZE, stream)) > 0) + offset += bytes_read; + + /* NULL terminate our buffer */ + buffer[filesize] = '\0'; + + fclose (stream); + return buffer; +} + +static char * +get_next_line (char *contents, char **next_start) +{ + char *p = contents; + + if (p == NULL || *p == '\0') { + *next_start = NULL; + return NULL; + } + + while (*p != '\n' && *p != '\0') + p++; + + if (*p == '\n') { + *p = '\0'; + *next_start = p + 1; + } else + *next_start = NULL; + + return contents; +} + +static void +init_suppressed_assemblies (void) +{ + char *content; + char *line; + + coverage_profiler.suppressed_assemblies = mono_conc_hashtable_new (g_str_hash, g_str_equal); + + /* Don't need to free content as it is referred to by the lines stored in @filters */ + content = get_file_content (SUPPRESSION_DIR "/mono-profiler-coverage.suppression"); + if (content == NULL) + content = get_file_content (SUPPRESSION_DIR "/mono-profiler-log.suppression"); + if (content == NULL) + return; + + while ((line = get_next_line (content, &content))) { + line = g_strchomp (g_strchug (line)); + /* No locking needed as we're doing initialization */ + mono_conc_hashtable_insert (coverage_profiler.suppressed_assemblies, line, line); + } +} + +static void +parse_cov_filter_file (GPtrArray *filters, const char *file) +{ + char *content; + char *line; + + /* Don't need to free content as it is referred to by the lines stored in @filters */ + content = get_file_content (file); + if (content == NULL) { + mono_profiler_printf_err ("Could not open coverage filter file '%s'.", file); + return; + } + + while ((line = get_next_line (content, &content))) + g_ptr_array_add (filters, g_strchug (g_strchomp (line))); +} + +static void +unref_coverage_assemblies (gpointer key, gpointer value, gpointer userdata) +{ + MonoAssembly *assembly = (MonoAssembly *)value; + mono_assembly_close (assembly); +} + +static void +log_shutdown (MonoProfiler *prof) +{ + g_assert (prof == &coverage_profiler); + + dump_coverage (); + + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_foreach (coverage_profiler.assemblies, unref_coverage_assemblies, NULL); + mono_os_mutex_unlock (&coverage_profiler.mutex); + + mono_conc_hashtable_destroy (coverage_profiler.methods); + mono_conc_hashtable_destroy (coverage_profiler.assemblies); + mono_conc_hashtable_destroy (coverage_profiler.classes); + mono_conc_hashtable_destroy (coverage_profiler.filtered_classes); + + mono_conc_hashtable_destroy (coverage_profiler.image_to_methods); + mono_conc_hashtable_destroy (coverage_profiler.suppressed_assemblies); + mono_os_mutex_destroy (&coverage_profiler.mutex); + + if (*coverage_config.output_filename == '|') { + pclose (coverage_profiler.file); + } else if (*coverage_config.output_filename == '#') { + // do nothing + } else { + fclose (coverage_profiler.file); + } + + g_free (coverage_profiler.args); +} + +static void +runtime_initialized (MonoProfiler *profiler) +{ + mono_counters_register ("Event: Coverage methods", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_methods_ctr); + mono_counters_register ("Event: Coverage statements", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_statements_ctr); + mono_counters_register ("Event: Coverage classes", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_classes_ctr); + mono_counters_register ("Event: Coverage assemblies", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_assemblies_ctr); +} + +static void usage (void); + +static gboolean +match_option (const char *arg, const char *opt_name, const char **rval) +{ + if (rval) { + const char *end = strchr (arg, '='); + + *rval = NULL; + if (!end) + return !strcmp (arg, opt_name); + + if (strncmp (arg, opt_name, strlen (opt_name)) || (end - arg) > strlen (opt_name) + 1) + return FALSE; + *rval = end + 1; + return TRUE; + } else { + //FIXME how should we handle passing a value to an arg that doesn't expect it? + return !strcmp (arg, opt_name); + } +} + +static void +parse_arg (const char *arg) +{ + const char *val; + + if (match_option (arg, "help", NULL)) { + usage (); + // } else if (match_option (arg, "zip", NULL)) { + // coverage_config.use_zip = TRUE; + } else if (match_option (arg, "output", &val)) { + coverage_config.output_filename = g_strdup (val); + // } else if (match_option (arg, "covfilter", &val)) { + // g_error ("not supported"); + } else if (match_option (arg, "covfilter-file", &val)) { + if (coverage_config.cov_filter_files == NULL) + coverage_config.cov_filter_files = g_ptr_array_new (); + g_ptr_array_add (coverage_config.cov_filter_files, g_strdup (val)); + } else { + mono_profiler_printf_err ("Could not parse argument: %s", arg); + } +} + +static void +parse_args (const char *desc) +{ + const char *p; + gboolean in_quotes = FALSE; + char quote_char = '\0'; + char *buffer = malloc (strlen (desc)); + int buffer_pos = 0; + + for (p = desc; *p; p++){ + switch (*p){ + case ',': + if (!in_quotes) { + if (buffer_pos != 0){ + buffer [buffer_pos] = 0; + parse_arg (buffer); + buffer_pos = 0; + } + } else { + buffer [buffer_pos++] = *p; + } + break; + + case '\\': + if (p [1]) { + buffer [buffer_pos++] = p[1]; + p++; + } + break; + case '\'': + case '"': + if (in_quotes) { + if (quote_char == *p) + in_quotes = FALSE; + else + buffer [buffer_pos++] = *p; + } else { + in_quotes = TRUE; + quote_char = *p; + } + break; + default: + buffer [buffer_pos++] = *p; + break; + } + } + + if (buffer_pos != 0) { + buffer [buffer_pos] = 0; + parse_arg (buffer); + } + + g_free (buffer); +} + +static void +usage (void) +{ + mono_profiler_printf ("Mono coverage profiler"); + mono_profiler_printf ("Usage: mono --profile=coverage[:OPTION1[,OPTION2...]] program.exe\n"); + mono_profiler_printf ("Options:"); + mono_profiler_printf ("\thelp show this usage info"); + + // mono_profiler_printf ("\tcovfilter=ASSEMBLY add ASSEMBLY to the code coverage filters"); + // mono_profiler_printf ("\t prefix a + to include the assembly or a - to exclude it"); + // mono_profiler_printf ("\t e.g. covfilter=-mscorlib"); + mono_profiler_printf ("\tcovfilter-file=FILE use FILE to generate the list of assemblies to be filtered"); + mono_profiler_printf ("\toutput=FILENAME write the data to file FILENAME (the file is always overwritten)"); + mono_profiler_printf ("\toutput=+FILENAME write the data to file FILENAME.pid (the file is always overwritten)"); + mono_profiler_printf ("\toutput=|PROGRAM write the data to the stdin of PROGRAM"); + mono_profiler_printf ("\toutput=|PROGRAM write the data to the stdin of PROGRAM"); + // mono_profiler_printf ("\tzip compress the output data"); +} + +MONO_API void +mono_profiler_init_coverage (const char *desc); + +void +mono_profiler_init_coverage (const char *desc) +{ + GPtrArray *filters = NULL; + + parse_args (desc [strlen("coverage")] == ':' ? desc + strlen ("coverage") + 1 : ""); + + if (coverage_config.cov_filter_files) { + filters = g_ptr_array_new (); + int i; + for (i = 0; i < coverage_config.cov_filter_files->len; ++i) { + const char *name = coverage_config.cov_filter_files->pdata [i]; + parse_cov_filter_file (filters, name); + } + } + + coverage_profiler.args = g_strdup (desc); + + //If coverage_config.output_filename begin with +, append the pid at the end + if (!coverage_config.output_filename) + coverage_config.output_filename = "coverage.xml"; + else if (*coverage_config.output_filename == '+') + coverage_config.output_filename = g_strdup_printf ("%s.%d", coverage_config.output_filename + 1, getpid ()); + + if (*coverage_config.output_filename == '|') + coverage_profiler.file = popen (coverage_config.output_filename + 1, "w"); + else if (*coverage_config.output_filename == '#') + coverage_profiler.file = fdopen (strtol (coverage_config.output_filename + 1, NULL, 10), "a"); + else + coverage_profiler.file = fopen (coverage_config.output_filename, "w"); + + if (!coverage_profiler.file) { + mono_profiler_printf_err ("Could not create coverage profiler output file '%s'.", coverage_config.output_filename); + exit (1); + } + + mono_os_mutex_init (&coverage_profiler.mutex); + coverage_profiler.methods = mono_conc_hashtable_new (NULL, NULL); + coverage_profiler.assemblies = mono_conc_hashtable_new (NULL, NULL); + coverage_profiler.classes = mono_conc_hashtable_new (NULL, NULL); + coverage_profiler.filtered_classes = mono_conc_hashtable_new (NULL, NULL); + coverage_profiler.image_to_methods = mono_conc_hashtable_new (NULL, NULL); + init_suppressed_assemblies (); + + coverage_profiler.filters = filters; + + MonoProfilerHandle handle = coverage_profiler.handle = mono_profiler_create (&coverage_profiler); + + /* + * Required callbacks. These are either necessary for the profiler itself + * to function, or provide metadata that's needed if other events (e.g. + * allocations, exceptions) are dynamically enabled/disabled. + */ + + mono_profiler_set_runtime_shutdown_end_callback (handle, log_shutdown); + mono_profiler_set_runtime_initialized_callback (handle, runtime_initialized); + + mono_profiler_enable_coverage (); + mono_profiler_set_coverage_filter_callback (handle, coverage_filter); +} diff --git a/mono/profiler/log-args.c b/mono/profiler/log-args.c index 1b9c8358694..3cfadd1e513 100644 --- a/mono/profiler/log-args.c +++ b/mono/profiler/log-args.c @@ -81,6 +81,7 @@ parse_arg (const char *arg, ProfilerConfig *config) } else if (match_option (arg, "calls", NULL)) { config->enter_leave = TRUE; } else if (match_option (arg, "coverage", NULL)) { + g_warning ("the log profiler support for code coverage is obsolete, use the \"coverage\" profiler"); config->collect_coverage = TRUE; } else if (match_option (arg, "zip", NULL)) { config->use_zip = TRUE; diff --git a/mono/tests/Makefile.am b/mono/tests/Makefile.am index 729b663f91b..77f06635dde 100755 --- a/mono/tests/Makefile.am +++ b/mono/tests/Makefile.am @@ -519,7 +519,8 @@ TESTS_CS_SRC= \ imt_big_iface_test.cs \ bug-58782-plain-throw.cs \ bug-58782-capture-and-throw.cs \ - recursive-struct-arrays.cs + recursive-struct-arrays.cs \ + bug-59281.cs if AMD64 TESTS_CS_SRC += async-exc-compilation.cs finally_guard.cs finally_block_ending_in_dead_bb.cs diff --git a/mono/tests/bug-59281.cs b/mono/tests/bug-59281.cs new file mode 100644 index 00000000000..94250c277a7 --- /dev/null +++ b/mono/tests/bug-59281.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading; + +class Driver +{ + + static readonly Mutex[] mutexes = new Mutex[2]; + + public static void Main(string[] args) + { + for (int i = 0; i < mutexes.Length; i++) { + mutexes [i] = new Mutex(); + } + + Thread thread1 = new Thread(() => { + for (int i = 0; i < 1; i++) { + int idx = -1; + try { + idx = WaitHandle.WaitAny (mutexes); + Console.WriteLine($"Thread 1 iter: {i} with mutex: {idx}"); + } finally { + if (idx != -1) + mutexes [idx].ReleaseMutex(); + } + } + + Console.WriteLine("Thread 1 ended"); + }); + + thread1.Start(); + thread1.Join(); + + Thread thread2 = new Thread(() => { + for (int i = 0; i < 1000; i++) { + int idx = -1; + try { + idx = WaitHandle.WaitAny (mutexes); + Console.WriteLine($"Thread 2 iter: {i} with mutex: {idx}"); + } finally { + if (idx != -1) + mutexes [idx].ReleaseMutex(); + } + } + + Console.WriteLine("Thread 2 ended"); + }); + + thread2.Start(); + thread2.Join(); + } +} \ No newline at end of file diff --git a/msvc/scripts/genproj.cs b/msvc/scripts/genproj.cs index c60bc5397ce..96337cadaaf 100644 --- a/msvc/scripts/genproj.cs +++ b/msvc/scripts/genproj.cs @@ -794,12 +794,8 @@ class MsbuildGenerator { var refs = new StringBuilder (); - bool is_test = response.Contains ("_test_"); - if (is_test) { - // F:\src\mono\mcs\class\lib\net_2_0\nunit.framework.dll - // F:\src\mono\mcs\class\SomeProject\SomeProject_test_-net_2_0.csproj - var nunitLibPath = string.Format (@"..\lib\{0}\nunit.framework.dll", profile); - refs.Append (string.Format (" " + NewLine, nunitLibPath)); + if (response.Contains ("_test")) { + refs.Append ($@" {NewLine}"); } // @@ -1049,7 +1045,7 @@ class MsbuildGenerator { public class Driver { - static IEnumerable GetProjects (bool full = false) + static IEnumerable GetProjects (bool withTests = false) { XDocument doc = XDocument.Load ("order.xml"); foreach (XElement project in doc.Root.Elements ()) { @@ -1071,12 +1067,6 @@ public class Driver { // if (!(dir.StartsWith ("class") || dir.StartsWith ("mcs") || dir.StartsWith ("basic"))) continue; - - if (full){ - if (!library.Contains ("tests")) - yield return project; - continue; - } #endif // // Do not do 2.1, it is not working yet @@ -1094,11 +1084,12 @@ public class Driver { if (dir.Contains ("nunit20")) continue; -#if true - if (profile != "net_4_x" || library.Contains ("tests")) + if (library.Contains ("tests") && !withTests) continue; -#endif - //Console.WriteLine ("Going to handle {0}", library); + + if (profile != "net_4_x") + continue; + yield return project; } } @@ -1112,12 +1103,12 @@ public class Driver { if (args.Length == 1 && args [0].ToLower ().Contains ("-h")) { Console.WriteLine ("Usage:"); - Console.WriteLine ("genproj.exe [visual_studio_release] [output_full_solutions]"); + Console.WriteLine ("genproj.exe [visual_studio_release] [output_full_solutions] [with_tests]"); Console.WriteLine ("If output_full_solutions is false, only the main System*.dll"); Console.WriteLine (" assemblies (and dependencies) is included in the solution."); Console.WriteLine ("Example:"); - Console.WriteLine ("genproj.exe 2012 false"); - Console.WriteLine ("genproj.exe with no arguments is equivalent to 'genproj.exe 2012 true'\n\n"); + Console.WriteLine ("genproj.exe 2012 false false"); + Console.WriteLine ("genproj.exe with no arguments is equivalent to 'genproj.exe 2012 true false'\n\n"); Console.WriteLine ("genproj.exe deps"); Console.WriteLine ("Generates a Makefile dependency file from the projects input"); Environment.Exit (0); @@ -1125,6 +1116,7 @@ public class Driver { var slnVersion = (args.Length > 0) ? args [0] : "2012"; bool fullSolutions = (args.Length > 1) ? bool.Parse (args [1]) : true; + bool withTests = (args.Length > 2) ? bool.Parse (args [2]) : false; // To generate makefile depenedencies var makefileDeps = (args.Length > 0 && args [0] == "deps"); @@ -1134,11 +1126,11 @@ public class Driver { var projects = new Dictionary (); var duplicates = new List (); - foreach (var project in GetProjects (makefileDeps)) { + foreach (var project in GetProjects (withTests)) { var library_output = project.Element ("library_output").Value; projects [library_output] = new MsbuildGenerator (project); } - foreach (var project in GetProjects (makefileDeps)){ + foreach (var project in GetProjects (withTests)){ var library_output = project.Element ("library_output").Value; //Console.WriteLine ("=== {0} ===", library_output); var gen = projects [library_output];