mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-12 12:26:29 +00:00
Allow the user to conveniently use Mercury libraries installed in
Estimated hours taken: 70 Allow the user to conveniently use Mercury libraries installed in non-standard places, by specifying the variables `EXTRA_LIBRARIES' and `EXTRA_LIB_DIRS' in their Mmakefile. doc/user_guide.texi: Documented the new `EXTRA_LIBRARIES' and `EXTRA_LIB_DIRS' Mmake variables. scripts/Mmake.vars.in: Added/changed definitions appropriately to support the new variables. This included adding a `GRADESTRING' variable which holds the canonical name of the grade being used, taking into account all the grade flags. scripts/c2init.in: Added a new option to `c2init': -I/--init-file-directory <directory> adds the directory to the list of directories to search for `.init' files. util/mkinit.c: Added a new option `-I' to add a directory to the list of directories to search for `.init' files.
This commit is contained in:
@@ -530,6 +530,18 @@ specified in @code{GRADEFLAGS} and @code{C2INITARGS}, respectively.)
|
||||
Extra files to be processed by c2init. These variables should not be
|
||||
used for specifying flags to c2init (that's what @code{C2INITFLAGS} is for)
|
||||
since they are also used to derive extra dependency information.
|
||||
|
||||
@item EXTRA_LIBRARIES
|
||||
A list of extra Mercury libraries to link into any programs or libraries
|
||||
that you are building.
|
||||
Libraries should be specified using their base name; that is, without any
|
||||
@samp{lib} prefix or extension.
|
||||
For example the library including the files @file{libfoo.a} and
|
||||
@file{foo.init} would be referred to as just @samp{foo}.
|
||||
|
||||
@item EXTRA_LIB_DIRS
|
||||
A list of extra Mercury library directory hierarchies to search when
|
||||
looking for extra libraries.
|
||||
@end table
|
||||
|
||||
Other variables also exist - see
|
||||
@@ -582,6 +594,7 @@ and, on many platforms, it supports shared object libraries.
|
||||
* Building libraries::
|
||||
* Installing libraries::
|
||||
* Using libraries::
|
||||
* Supporting multiple grades and architectures::
|
||||
@end menu
|
||||
|
||||
@node Writing libraries
|
||||
@@ -779,6 +792,45 @@ for each Mercury library being used are all put in a single directory,
|
||||
which is probably the simplest way of organizing things, but the
|
||||
Mercury implementation does not require that.
|
||||
|
||||
@node Supporting multiple grades and architectures
|
||||
@section Supporting multiple grades and architectures
|
||||
|
||||
In order to better support using and installing libraries in multiple
|
||||
grades, @samp{mmake} now has some support for alternative library directory
|
||||
hierarchies.
|
||||
These have the same structure as the @file{@var{prefix}/lib/mercury} tree,
|
||||
including the different subdirectories for different grades and different
|
||||
machine architectures.
|
||||
|
||||
Automatically installing a library and all its associated files to such a
|
||||
tree is not yet supported, but until it is the user can use the same
|
||||
techniques used for building and installing the core Mercury libraries.
|
||||
|
||||
Once a library is installed in such a hierarchy, using it is easy.
|
||||
Suppose the user wishes to use the library @samp{mypackage} (installed
|
||||
in the tree rooted at @samp{/some/directory/mypackage}) and the library
|
||||
@samp{myotherlib} (installed in the tree rooted at
|
||||
@samp{/some/directory/myotherlib}).
|
||||
The user need only set the following Mmake variables:
|
||||
|
||||
@example
|
||||
EXTRA_LIB_DIRS = /some/directory/mypackage /some/directory/myotherlib
|
||||
EXTRA_LIBRARIES = mypackage myotherlib
|
||||
@end example
|
||||
|
||||
Mmake will then ensure that the appropriate directories are searched for
|
||||
the relevant interface files, module initialisation files, compiled
|
||||
libraries, etc.
|
||||
|
||||
One can specify extra libraries to be used on a program-by-program
|
||||
basis. For instance, if the program @samp{foo} also uses the library
|
||||
@samp{mylib4foo}, but the other programs governed by the Mmakefile don't,
|
||||
then one can declare:
|
||||
|
||||
@example
|
||||
EXTRA_LIBRARIES-foo = mylib4foo
|
||||
@end example
|
||||
|
||||
@node Debugging
|
||||
@chapter Debugging
|
||||
|
||||
|
||||
@@ -12,9 +12,31 @@
|
||||
# Ensure that commands use /bin/sh not the user's shell
|
||||
SHELL = /bin/sh
|
||||
|
||||
ALL_EXTRA_LIBRARIES = $(TARGET_EXTRA_LIBRARIES) $(EXTRA_LIBRARIES)
|
||||
EXTRA_LIB_DIRS =
|
||||
EXTRA_LIBRARIES =
|
||||
|
||||
EXTRA_INT_DIRS = $(patsubst %,%/ints,$(EXTRA_LIB_DIRS))
|
||||
MERCURY_EXTRA_INT_DIRS = $(EXTRA_INT_DIRS)
|
||||
EXTRA_C_LIB_DIRS = \
|
||||
$(patsubst %,%/lib/$(GRADESTRING)/@FULLARCH@,$(EXTRA_LIB_DIRS)) \
|
||||
$(patsubst %,%/lib/@FULLARCH@,$(EXTRA_LIB_DIRS))
|
||||
EXTRA_C_INCL_DIRS = $(patsubst %,%/inc,$(EXTRA_LIB_DIRS))
|
||||
EXTRA_INIT_DIRS = $(patsubst %,%/modules,$(EXTRA_LIB_DIRS))
|
||||
MERCURY_EXTRA_INIT_DIRS = $(EXTRA_INIT_DIRS)
|
||||
|
||||
# Set the directory search path.
|
||||
# (See the GNU Make manual for documentation about VPATH and GPATH.)
|
||||
MMAKE_VPATH = $(MERCURY_INT_DIR)
|
||||
# We need to substitute all spaces with colons for the VPATH to work.
|
||||
# Getting a space to be recognised as the first argument of the subst
|
||||
# function is problematic; hence the `$(nullstring)' hack.
|
||||
# XXX Note that directory names with spaces in them (e.g. under Windows)
|
||||
# will cause problems for the VPATH settings below, as well as every
|
||||
# occurrence of $(patsubst ...), since GNU Make does not respect quotes.
|
||||
nullstring =
|
||||
MMAKE_VPATH = $(subst $(nullstring) ,:,$(strip \
|
||||
$(MERCURY_EXTRA_INT_DIRS) $(MERCURY_INT_DIR)\
|
||||
$(MERCURY_EXTRA_INIT_DIRS)))
|
||||
VPATH = $(MMAKE_VPATH) # do not remove the `:' from this comment!!!
|
||||
# the above comment works around a misfeature of
|
||||
# autoconf which causes it to delete assignments to
|
||||
@@ -23,15 +45,17 @@ GPATH = $(VPATH)
|
||||
|
||||
DEFAULT_GRADE = $(MERCURY_DEFAULT_GRADE)
|
||||
GRADE = $(DEFAULT_GRADE)
|
||||
GRADESTRING = $(shell $(MCOGS) $(ALL_GRADEFLAGS))
|
||||
|
||||
ALL_GRADEFLAGS = $(GRADEFLAGS) $(EXTRA_GRADEFLAGS) $(TARGET_GRADEFLAGS)
|
||||
GRADEFLAGS = --grade $(GRADE)
|
||||
EXTRA_GRADEFLAGS =
|
||||
|
||||
MC = mmc
|
||||
ALL_MCFLAGS = $(MCFLAGS) $(EXTRA_MCFLAGS) $(TARGET_MCFLAGS)
|
||||
ALL_MCFLAGS = $(MCFLAGS) $(EXTRA_MCFLAGS) $(TARGET_MCFLAGS) $(LIB_MCFLAGS)
|
||||
MCFLAGS =
|
||||
EXTRA_MCFLAGS =
|
||||
LIB_MCFLAGS = $(patsubst %,-I %,$(EXTRA_INT_DIRS))
|
||||
|
||||
MCS = $(MC) --split-c-files --compile-only
|
||||
MCG = $(MC) --compile-to-c
|
||||
@@ -41,15 +65,24 @@ MCPI = $(MC) --make-private-interface
|
||||
MCSI = $(MC) --make-short-interface
|
||||
MCOI = $(MC) --make-optimization-interface
|
||||
MCTOI = $(MC) --make-transitive-optimization-interface
|
||||
MCOGS = $(MC) --output-grade-string
|
||||
|
||||
ALL_MCIFLAGS = $(MCIFLAGS) $(EXTRA_MCIFLAGS) $(TARGET_MCFLAGS)
|
||||
ALL_MCPIFLAGS = $(MCPIFLAGS) $(EXTRA_MCPIFLAGS) $(TARGET_MCFLAGS)
|
||||
ALL_MCSIFLAGS = $(MCSIFLAGS) $(EXTRA_MCSIFLAGS) $(TARGET_MCFLAGS)
|
||||
ALL_MCOIFLAGS = $(MCOIFLAGS) $(EXTRA_MCOIFLAGS) $(TARGET_MCFLAGS)
|
||||
ALL_MCTOIFLAGS = $(MCTOIFLAGS) $(EXTRA_MCTOIFLAGS) $(TARGET_MCFLAGS)
|
||||
ALL_MCDFLAGS = $(MCDFLAGS) $(EXTRA_MCDFLAGS) $(TARGET_MCFLAGS)
|
||||
ALL_MCGFLAGS = $(MCGFLAGS) $(EXTRA_MCGFLAGS) $(TARGET_MCFLAGS)
|
||||
ALL_MCSFLAGS = $(MCSFLAGS) $(EXTRA_MCSFLAGS) $(TARGET_MCFLAGS)
|
||||
ALL_MCIFLAGS = $(MCIFLAGS) $(EXTRA_MCIFLAGS) $(TARGET_MCFLAGS) \
|
||||
$(LIB_MCFLAGS)
|
||||
ALL_MCPIFLAGS = $(MCPIFLAGS) $(EXTRA_MCPIFLAGS) $(TARGET_MCFLAGS) \
|
||||
$(LIB_MCFLAGS)
|
||||
ALL_MCSIFLAGS = $(MCSIFLAGS) $(EXTRA_MCSIFLAGS) $(TARGET_MCFLAGS) \
|
||||
$(LIB_MCFLAGS)
|
||||
ALL_MCOIFLAGS = $(MCOIFLAGS) $(EXTRA_MCOIFLAGS) $(TARGET_MCFLAGS) \
|
||||
$(LIB_MCFLAGS)
|
||||
ALL_MCTOIFLAGS = $(MCTOIFLAGS) $(EXTRA_MCTOIFLAGS) $(TARGET_MCFLAGS) \
|
||||
$(LIB_MCFLAGS)
|
||||
ALL_MCDFLAGS = $(MCDFLAGS) $(EXTRA_MCDFLAGS) $(TARGET_MCFLAGS) \
|
||||
$(LIB_MCFLAGS)
|
||||
ALL_MCGFLAGS = $(MCGFLAGS) $(EXTRA_MCGFLAGS) $(TARGET_MCFLAGS) \
|
||||
$(LIB_MCFLAGS)
|
||||
ALL_MCSFLAGS = $(MCSFLAGS) $(EXTRA_MCSFLAGS) $(TARGET_MCFLAGS) \
|
||||
$(LIB_MCFLAGS)
|
||||
|
||||
MCIFLAGS = $(MCFLAGS)
|
||||
MCPIFLAGS = $(MCFLAGS)
|
||||
@@ -74,13 +107,17 @@ EXTRA_MCSFLAGS = $(EXTRA_MCFLAGS)
|
||||
# the options which should be passed to them.
|
||||
#
|
||||
C2INIT = c2init
|
||||
ALL_C2INITFLAGS = $(C2INITFLAGS) $(EXTRA_C2INITFLAGS) $(TARGET_C2INITFLAGS)
|
||||
ALL_C2INITFLAGS = $(C2INITFLAGS) $(EXTRA_C2INITFLAGS) $(TARGET_C2INITFLAGS) \
|
||||
$(LIB_C2INITFLAGS)
|
||||
C2INITFLAGS =
|
||||
EXTRA_C2INITFLAGS =
|
||||
LIB_C2INITFLAGS = $(patsubst %,--init-file-directory %,$(EXTRA_INIT_DIRS))
|
||||
|
||||
ALL_C2INITARGS = $(C2INITARGS) $(EXTRA_C2INITARGS) $(TARGET_C2INITARGS)
|
||||
ALL_C2INITARGS = $(C2INITARGS) $(EXTRA_C2INITARGS) $(TARGET_C2INITARGS) \
|
||||
$(LIB_C2INITARGS)
|
||||
C2INITARGS =
|
||||
EXTRA_C2INITARGS =
|
||||
LIB_C2INITARGS = $(patsubst %,%.init,$(ALL_EXTRA_LIBRARIES))
|
||||
|
||||
MGNUC = mgnuc
|
||||
ALL_MGNUCFLAGS = $(MGNUCFLAGS) $(EXTRA_MGNUCFLAGS) $(TARGET_MGNUCFLAGS) \
|
||||
@@ -93,13 +130,15 @@ CFLAGS =
|
||||
EXTRA_CFLAGS =
|
||||
|
||||
ML = ml
|
||||
ALL_MLFLAGS = $(MLFLAGS) $(EXTRA_MLFLAGS) $(TARGET_MLFLAGS)
|
||||
ALL_MLFLAGS = $(MLFLAGS) $(EXTRA_MLFLAGS) $(TARGET_MLFLAGS) $(LIB_MLFLAGS)
|
||||
MLFLAGS = $(EXTRA_MLFLAGS)
|
||||
EXTRA_MLFLAGS =
|
||||
LIB_MLFLAGS = $(patsubst %,-R%,$(EXTRA_C_LIB_DIRS)) \
|
||||
$(patsubst %,-L%,$(EXTRA_C_LIB_DIRS))
|
||||
|
||||
MLOBJS =
|
||||
MLPICOBJS = $(MLOBJS:.o=.$(EXT_FOR_PIC_OBJECTS))
|
||||
ALL_MLLIBS = $(MLLIBS) $(EXTRA_MLLIBS) $(TARGET_MLLIBS)
|
||||
ALL_MLLIBS = $(MLLIBS) $(EXTRA_MLLIBS) $(TARGET_MLLIBS) $(LIB_MLLIBS)
|
||||
# XXX ALL_MLLIBS_DEP should contain a list of the file names of the
|
||||
# libraries specified in ALL_MLLIBS, but I can't see how to know whether
|
||||
# they should be `.a' or `.so' libraries, so for now we leave it empty.
|
||||
@@ -107,6 +146,7 @@ ALL_MLLIBS = $(MLLIBS) $(EXTRA_MLLIBS) $(TARGET_MLLIBS)
|
||||
ALL_MLLIBS_DEP =
|
||||
MLLIBS =
|
||||
EXTRA_MLLIBS =
|
||||
LIB_MLLIBS = $(patsubst %,-l%,$(ALL_EXTRA_LIBRARIES))
|
||||
|
||||
MNC = mnc
|
||||
ALL_MNCFLAGS = $(MNCFLAGS) $(EXTRA_MNCFLAGS) $(TARGET_MNCFLAGS)
|
||||
@@ -256,6 +296,15 @@ TARGET_RANLIBFLAGS = \
|
||||
maybe-target-RANLIBFLAGS- = $(RANLIBFLAGS-$@)
|
||||
maybe-target-RANLIBFLAGS-undefined =
|
||||
|
||||
# Note we strip any trailing `_init.c' from `$@' so we get the appropriate
|
||||
# "base" name, regardless of whether this variable ends up being used as
|
||||
# an argument of a `c2init' rule or an `ml' rule.
|
||||
TARGET_EXTRA_LIBRARIES = \
|
||||
$(maybe-target-EXTRA_LIBRARIES-$(findstring undefined,\
|
||||
$(origin EXTRA_LIBRARIES-$(patsubst %_init.c,%,$@))))
|
||||
maybe-target-EXTRA_LIBRARIES- = $(EXTRA_LIBRARIES-$(patsubst %_init.c,%,$@))
|
||||
maybe-target-EXTRA_LIBRARIES-undefined =
|
||||
|
||||
# Support for compiling Mercury programs with Prolog will probably be
|
||||
# dropped one of these days, so it's probably not worth bothering with these.
|
||||
TARGET_MNCFLAGS =
|
||||
|
||||
@@ -41,6 +41,9 @@ Options:
|
||||
(declared in \"init.h\") that can be called from C code.
|
||||
(A more fine-grained interface is also available;
|
||||
see \"init.h\" for details.)
|
||||
-I <directory>, --init-file-directory <directory>
|
||||
Include <directory> in the list of directories searched to
|
||||
locate \`.init' files.
|
||||
-w <label>, --entry-point <label>
|
||||
Set entry point to <label>.
|
||||
(Default value is \`mercury__main_2_0'.)
|
||||
@@ -88,6 +91,7 @@ trace_opt=""
|
||||
library_opt=""
|
||||
extra_inits_opt=""
|
||||
aditi_opt=""
|
||||
extra_init_dirs=""
|
||||
|
||||
# include the file `init_grade_options.sh-subr'
|
||||
@INIT_GRADE_OPTIONS@
|
||||
@@ -113,6 +117,9 @@ while true; do
|
||||
-l-|--no-library)
|
||||
library_opt="";;
|
||||
|
||||
-I|--init-file-directory)
|
||||
extra_init_dirs="$extra_init_dirs -I $2"; shift;;
|
||||
|
||||
-w|--entry-point)
|
||||
defentry="$2"; shift;;
|
||||
|
||||
@@ -176,10 +183,10 @@ esac
|
||||
case $# in
|
||||
0) exec $MKINIT $aditi_opt -c"$maxcalls" $init_opt $trace_opt \
|
||||
$library_opt -w"$defentry" $extra_inits_opt \
|
||||
$MERCURY_MOD_LIB_MODS
|
||||
$extra_init_dirs $EXTRA_INIT_FILES $MERCURY_MOD_LIB_MODS
|
||||
;;
|
||||
*) exec $MKINIT $aditi_opt -c"$maxcalls" $init_opt $trace_opt \
|
||||
$library_opt -w"$defentry" $extra_inits_opt "$@" \
|
||||
$MERCURY_MOD_LIB_MODS
|
||||
$library_opt -w"$defentry" $extra_inits_opt \
|
||||
$extra_init_dirs "$@" $EXTRA_INIT_FILES $MERCURY_MOD_LIB_MODS
|
||||
;;
|
||||
esac
|
||||
|
||||
115
util/mkinit.c
115
util/mkinit.c
@@ -23,6 +23,8 @@
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "getopt.h"
|
||||
#include "mercury_conf.h"
|
||||
#include "mercury_std.h"
|
||||
@@ -32,7 +34,7 @@
|
||||
#define MAXLINE 256 /* maximum number of characters per line */
|
||||
/* (characters after this limit are ignored) */
|
||||
|
||||
/* --- used to collect Aditi data constant names --- */
|
||||
/* --- used to collect a list of strings, e.g. Aditi data constant names --- */
|
||||
|
||||
typedef struct String_List_struct {
|
||||
char *data;
|
||||
@@ -57,6 +59,12 @@ static bool need_tracing = FALSE;
|
||||
static int num_modules = 0;
|
||||
static int num_errors = 0;
|
||||
|
||||
/* List of directories to search for init files */
|
||||
static String_List *init_file_dirs = NULL;
|
||||
|
||||
/* Pointer to tail of the init_file_dirs list */
|
||||
static String_List **init_file_dirs_tail = &init_file_dirs;
|
||||
|
||||
/* List of names of Aditi-RL code constants. */
|
||||
static String_List *rl_data = NULL;
|
||||
|
||||
@@ -229,6 +237,9 @@ static const char if_need_to_init[] =
|
||||
/* --- function prototypes --- */
|
||||
static void parse_options(int argc, char *argv[]);
|
||||
static void usage(void);
|
||||
static void do_path_search(void);
|
||||
static char *find_init_file(const char *basename);
|
||||
static bool file_exists(const char *filename);
|
||||
static void output_headers(void);
|
||||
static void output_sub_init_functions(void);
|
||||
static void output_main_init_function(void);
|
||||
@@ -279,6 +290,8 @@ main(int argc, char **argv)
|
||||
|
||||
parse_options(argc, argv);
|
||||
|
||||
do_path_search();
|
||||
|
||||
output_headers();
|
||||
output_sub_init_functions();
|
||||
output_main_init_function();
|
||||
@@ -304,8 +317,9 @@ main(int argc, char **argv)
|
||||
static void
|
||||
parse_options(int argc, char *argv[])
|
||||
{
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "ac:iltw:x")) != EOF) {
|
||||
int c;
|
||||
String_List *tmp_slist;
|
||||
while ((c = getopt(argc, argv, "ac:iI:ltw:x")) != EOF) {
|
||||
switch (c) {
|
||||
case 'a':
|
||||
aditi = TRUE;
|
||||
@@ -320,6 +334,21 @@ parse_options(int argc, char *argv[])
|
||||
need_initialization_code = TRUE;
|
||||
break;
|
||||
|
||||
case 'I':
|
||||
/*
|
||||
** Add the directory name to the end of the
|
||||
** search path for `.init' files.
|
||||
*/
|
||||
tmp_slist = (String_List *)
|
||||
checked_malloc(sizeof(String_List));
|
||||
tmp_slist->next = NULL;
|
||||
tmp_slist->data = (char *)
|
||||
checked_malloc(strlen(optarg) + 1);
|
||||
strcpy(tmp_slist->data, optarg);
|
||||
*init_file_dirs_tail = tmp_slist;
|
||||
init_file_dirs_tail = &tmp_slist->next;
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
output_main_func = FALSE;
|
||||
break;
|
||||
@@ -355,6 +384,86 @@ usage(void)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
** Scan the list of files for ones not found in the current
|
||||
** directory, and replace them with their full path equivalent
|
||||
** if they are found in the list of search directories.
|
||||
*/
|
||||
static void
|
||||
do_path_search(void)
|
||||
{
|
||||
int filenum;
|
||||
char *init_file;
|
||||
|
||||
for (filenum = 0; filenum < num_files; filenum++) {
|
||||
init_file = find_init_file(files[filenum]);
|
||||
if (init_file != NULL)
|
||||
files[filenum] = init_file;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Search the init file directory list to locate the file.
|
||||
** If the file is in the current directory or is not in any of the
|
||||
** search directories, then return NULL. Otherwise return the full
|
||||
** path name to the file.
|
||||
** It is the caller's responsibility to free the returned buffer
|
||||
** holding the full path name when it is no longer needed.
|
||||
*/
|
||||
static char *
|
||||
find_init_file(const char *basename)
|
||||
{
|
||||
char *filename;
|
||||
char *dirname;
|
||||
String_List *dir_ptr;
|
||||
int dirlen;
|
||||
int baselen;
|
||||
int len;
|
||||
|
||||
if (file_exists(basename)) {
|
||||
/* File is in current directory, so no search required */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
baselen = strlen(basename);
|
||||
|
||||
for (dir_ptr = init_file_dirs; dir_ptr != NULL;
|
||||
dir_ptr = dir_ptr->next)
|
||||
{
|
||||
dirname = dir_ptr->data;
|
||||
dirlen = strlen(dirname);
|
||||
len = dirlen + 1 + baselen;
|
||||
|
||||
filename = (char *) checked_malloc(len + 1);
|
||||
strcpy(filename, dirname);
|
||||
filename[dirlen] = '/';
|
||||
strcpy(filename + dirlen + 1, basename);
|
||||
|
||||
if (file_exists(filename))
|
||||
return filename;
|
||||
|
||||
free(filename);
|
||||
}
|
||||
|
||||
/* Did not find file */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
** Check whether a file exists.
|
||||
** At some point in the future it may be worth making this
|
||||
** implementation more portable.
|
||||
*/
|
||||
static bool
|
||||
file_exists(const char *filename)
|
||||
{
|
||||
struct stat buf;
|
||||
|
||||
return (stat(filename, &buf) == 0);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
|
||||
Reference in New Issue
Block a user