mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-22 15:45:08 +00:00
Windows build script (#154)
* Cast from float explicitly. * Upgrades to waf 2.0.2. For VS2017 support on Windows. * [waf] Supports building on Windows. Needs 7z.exe in PATH for extracting prebuilt clang NSIS installer (when using bundled clang). RPATH is "emulated" by putting a libclang.dll alongside the cquery binary. Tested with Visual Studio 2017 toolchain. * [waf] Fix default_resource_directory on Windows + system clang. * Add AppVeyor configuration
This commit is contained in:
parent
0a8ea0e51f
commit
0045e4817c
32
.appveyor.yml
Normal file
32
.appveyor.yml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
version: "{build}"
|
||||||
|
image: Visual Studio 2017
|
||||||
|
platform: x64
|
||||||
|
|
||||||
|
clone_folder: C:\projects\cquery
|
||||||
|
|
||||||
|
install:
|
||||||
|
- git submodule update --init
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- cd C:\projects\cquery
|
||||||
|
- python waf configure --msvc_version="msvc 14.0" --msvc_targets=x64
|
||||||
|
- python waf build
|
||||||
|
- ps: |
|
||||||
|
$dir = "cquery-${env:appveyor_build_version}-win64"
|
||||||
|
cd C:\projects\cquery
|
||||||
|
mkdir "${dir}"
|
||||||
|
mkdir "${dir}\build"
|
||||||
|
mkdir "${dir}\build\release"
|
||||||
|
mkdir "${dir}\build\release\bin"
|
||||||
|
copy "build\release\bin\*" "${dir}\build\release\bin"
|
||||||
|
copy -recurse "clang_resource_dir" "${dir}\clang_resource_dir"
|
||||||
|
7z a -tzip "C:\projects\cquery\${dir}.zip" "${dir}"
|
||||||
|
|
||||||
|
artifacts:
|
||||||
|
- path: 'cquery-*.zip'
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- C:\projects\cquery\build\LLVM-4.0.0-win64.exe
|
||||||
|
- C:\projects\cquery\build\LLVM-5.0.0-win64.exe
|
||||||
|
- C:\projects\cquery\build\LLVM-4.0.0-win64\
|
||||||
|
- C:\projects\cquery\build\LLVM-5.0.0-win64\
|
@ -153,9 +153,9 @@ struct InitializeHandler : BaseMessageHandler<Ipc_InitializeRequest> {
|
|||||||
if (config->indexerCount == 0) {
|
if (config->indexerCount == 0) {
|
||||||
// If the user has not specified how many indexers to run, try to
|
// If the user has not specified how many indexers to run, try to
|
||||||
// guess an appropriate value. Default to 80% utilization.
|
// guess an appropriate value. Default to 80% utilization.
|
||||||
const float kDefaultTargetUtilization = 0.8;
|
const float kDefaultTargetUtilization = 0.8f;
|
||||||
config->indexerCount =
|
config->indexerCount = (int)(
|
||||||
std::thread::hardware_concurrency() * kDefaultTargetUtilization;
|
std::thread::hardware_concurrency() * kDefaultTargetUtilization);
|
||||||
if (config->indexerCount <= 0)
|
if (config->indexerCount <= 0)
|
||||||
config->indexerCount = 1;
|
config->indexerCount = 1;
|
||||||
}
|
}
|
||||||
|
124
wscript
124
wscript
@ -11,6 +11,8 @@ import string
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
import ctypes
|
||||||
|
import shutil
|
||||||
|
|
||||||
VERSION = '0.0.1'
|
VERSION = '0.0.1'
|
||||||
APPNAME = 'cquery'
|
APPNAME = 'cquery'
|
||||||
@ -24,6 +26,7 @@ out = 'build'
|
|||||||
# http://releases.llvm.org/5.0.0/clang+llvm-5.0.0-linux-x86_64-ubuntu14.04.tar.xz
|
# http://releases.llvm.org/5.0.0/clang+llvm-5.0.0-linux-x86_64-ubuntu14.04.tar.xz
|
||||||
# http://releases.llvm.org/5.0.0/clang+llvm-5.0.0-x86_64-apple-darwin.tar.xz
|
# http://releases.llvm.org/5.0.0/clang+llvm-5.0.0-x86_64-apple-darwin.tar.xz
|
||||||
|
|
||||||
|
CLANG_TARBALL_EXT = '.tar.xz'
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
CLANG_TARBALL_NAME = 'clang+llvm-$version-x86_64-apple-darwin'
|
CLANG_TARBALL_NAME = 'clang+llvm-$version-x86_64-apple-darwin'
|
||||||
elif sys.platform.startswith('freebsd'):
|
elif sys.platform.startswith('freebsd'):
|
||||||
@ -32,14 +35,44 @@ elif sys.platform.startswith('freebsd'):
|
|||||||
elif sys.platform.startswith('linux'):
|
elif sys.platform.startswith('linux'):
|
||||||
# These executable depend on libtinfo.so.5
|
# These executable depend on libtinfo.so.5
|
||||||
CLANG_TARBALL_NAME = 'clang+llvm-$version-linux-x86_64-ubuntu14.04'
|
CLANG_TARBALL_NAME = 'clang+llvm-$version-linux-x86_64-ubuntu14.04'
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
CLANG_TARBALL_NAME = 'LLVM-$version-win64'
|
||||||
|
CLANG_TARBALL_EXT = '.exe'
|
||||||
else:
|
else:
|
||||||
# TODO: windows support (it's an exe!)
|
|
||||||
sys.stderr.write('ERROR: Unknown platform {0}\n'.format(sys.platform))
|
sys.stderr.write('ERROR: Unknown platform {0}\n'.format(sys.platform))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
from waflib.Tools.compiler_cxx import cxx_compiler
|
from waflib.Tools.compiler_cxx import cxx_compiler
|
||||||
cxx_compiler['linux'] = ['clang++', 'g++']
|
cxx_compiler['linux'] = ['clang++', 'g++']
|
||||||
|
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
kdll = ctypes.windll.kernel32
|
||||||
|
def symlink(source, link_name, target_is_directory=False):
|
||||||
|
# SYMBOLIC_LINK_FLAG_DIRECTORY: 0x1
|
||||||
|
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2
|
||||||
|
flags = int(target_is_directory)
|
||||||
|
ret = kdll.CreateSymbolicLinkA(link_name, source, flags)
|
||||||
|
if ret == 0:
|
||||||
|
err = ctypes.WinError()
|
||||||
|
ERROR_PRIVILEGE_NOT_HELD = 1314
|
||||||
|
# Creating symbolic link on Windows requires a special priviledge SeCreateSymboliclinkPrivilege,
|
||||||
|
# which an non-elevated process lacks. Starting with Windows 10 build 14972, this got relaxed
|
||||||
|
# when Developer Mode is enabled. Triggering this new behaviour requires a new flag. Try again.
|
||||||
|
if err[0] == ERROR_PRIVILEGE_NOT_HELD:
|
||||||
|
flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
|
||||||
|
ret = kdll.CreateSymbolicLinkA(link_name, source, flags)
|
||||||
|
if ret != 0:
|
||||||
|
return
|
||||||
|
err = ctypes.WinError()
|
||||||
|
raise err
|
||||||
|
else:
|
||||||
|
# Python 3 compatibility
|
||||||
|
real_symlink = os.symlink
|
||||||
|
def symlink(source, link_name, target_is_directory=False):
|
||||||
|
return real_symlink(source, link_name)
|
||||||
|
os.symlink = symlink
|
||||||
|
|
||||||
def options(opt):
|
def options(opt):
|
||||||
opt.load('compiler_cxx')
|
opt.load('compiler_cxx')
|
||||||
grp = opt.add_option_group('Configuration options related to use of clang from the system (not recommended)')
|
grp = opt.add_option_group('Configuration options related to use of clang from the system (not recommended)')
|
||||||
@ -54,8 +87,8 @@ def options(opt):
|
|||||||
grp.add_option('--variant', default='release',
|
grp.add_option('--variant', default='release',
|
||||||
help='variant name for saving configuration and build results. Variants other than "debug" turn on -O3')
|
help='variant name for saving configuration and build results. Variants other than "debug" turn on -O3')
|
||||||
|
|
||||||
def download_and_extract(destdir, url):
|
def download_and_extract(destdir, url, ext):
|
||||||
dest = destdir + '.tar.xz'
|
dest = destdir + ext
|
||||||
# Download and save the compressed tarball as |compressed_file_name|.
|
# Download and save the compressed tarball as |compressed_file_name|.
|
||||||
if not os.path.isfile(dest):
|
if not os.path.isfile(dest):
|
||||||
print('Downloading tarball')
|
print('Downloading tarball')
|
||||||
@ -72,7 +105,10 @@ def download_and_extract(destdir, url):
|
|||||||
if not os.path.isdir(destdir):
|
if not os.path.isdir(destdir):
|
||||||
print('Extracting')
|
print('Extracting')
|
||||||
# TODO: make portable.
|
# TODO: make portable.
|
||||||
subprocess.call(['tar', '-x', '-C', out, '-f', dest])
|
if ext == '.exe':
|
||||||
|
subprocess.call(['7z', 'x', '-o{0}'.format(destdir), '-xr!$PLUGINSDIR', dest])
|
||||||
|
else:
|
||||||
|
subprocess.call(['tar', '-x', '-C', out, '-f', dest])
|
||||||
else:
|
else:
|
||||||
print('Found extracted at {0}'.format(destdir))
|
print('Found extracted at {0}'.format(destdir))
|
||||||
|
|
||||||
@ -80,13 +116,20 @@ def configure(ctx):
|
|||||||
ctx.resetenv(ctx.options.variant)
|
ctx.resetenv(ctx.options.variant)
|
||||||
|
|
||||||
ctx.load('compiler_cxx')
|
ctx.load('compiler_cxx')
|
||||||
if not ctx.env.CXXFLAGS:
|
cxxflags = ['-g', '-std=c++11', '-Wall', '-Wno-sign-compare', '-Werror']
|
||||||
|
# /Zi: -g, /WX: -Werror, /W3: roughly -Wall, there is no -std=c++11 equivalent in MSVC.
|
||||||
|
# /wd4722: ignores warning C4722 (destructor never returns) in loguru
|
||||||
|
# /wd4267: ignores warning C4267 (conversion from 'size_t' to 'type'), roughly -Wno-sign-compare
|
||||||
|
msvcflags = ['/nologo', '/FS', '/EHsc', '/Zi', '/W3', '/WX', '/wd4996', '/wd4722', '/wd4267', '/wd4800']
|
||||||
|
if ctx.options.variant != 'debug':
|
||||||
|
cxxflags.append('-O3')
|
||||||
|
msvcflags.append('/O2') # There is no O3
|
||||||
|
if ctx.env.CXX_NAME != 'msvc':
|
||||||
# If environment variable CXXFLAGS is unset, provide a sane default.
|
# If environment variable CXXFLAGS is unset, provide a sane default.
|
||||||
cxxflags = ['-g', '-std=c++11', '-Wall', '-Wno-sign-compare', '-Werror']
|
if not ctx.env.CXXFLAGS:
|
||||||
if ctx.options.variant != 'debug':
|
|
||||||
ctx.env.CXXFLAGS = cxxflags + ['-O3']
|
|
||||||
else:
|
|
||||||
ctx.env.CXXFLAGS = cxxflags
|
ctx.env.CXXFLAGS = cxxflags
|
||||||
|
else:
|
||||||
|
ctx.env.CXXFLAGS = msvcflags
|
||||||
|
|
||||||
ctx.check(header_name='stdio.h', features='cxx cxxprogram', mandatory=True)
|
ctx.check(header_name='stdio.h', features='cxx cxxprogram', mandatory=True)
|
||||||
|
|
||||||
@ -94,6 +137,11 @@ def configure(ctx):
|
|||||||
|
|
||||||
ctx.env['use_system_clang'] = ctx.options.use_system_clang
|
ctx.env['use_system_clang'] = ctx.options.use_system_clang
|
||||||
ctx.env['bundled_clang'] = ctx.options.bundled_clang
|
ctx.env['bundled_clang'] = ctx.options.bundled_clang
|
||||||
|
def libname(lib):
|
||||||
|
# Newer MinGW and MSVC both wants full file name
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
return 'lib' + lib
|
||||||
|
return lib
|
||||||
if ctx.options.use_system_clang:
|
if ctx.options.use_system_clang:
|
||||||
# Ask llvm-config for cflags and ldflags
|
# Ask llvm-config for cflags and ldflags
|
||||||
ctx.find_program(ctx.options.llvm_config, msg='checking for llvm-config', var='LLVM_CONFIG', mandatory=False)
|
ctx.find_program(ctx.options.llvm_config, msg='checking for llvm-config', var='LLVM_CONFIG', mandatory=False)
|
||||||
@ -105,7 +153,7 @@ def configure(ctx):
|
|||||||
args='--cppflags --ldflags')
|
args='--cppflags --ldflags')
|
||||||
# llvm-config does not provide the actual library we want so we check for it
|
# llvm-config does not provide the actual library we want so we check for it
|
||||||
# using the provided info so far.
|
# using the provided info so far.
|
||||||
ctx.check_cxx(lib='clang', uselib_store='clang', use='clang')
|
ctx.check_cxx(lib=libname('clang'), uselib_store='clang', use='clang')
|
||||||
|
|
||||||
else: # Fallback method using a prefix path
|
else: # Fallback method using a prefix path
|
||||||
ctx.start_msg('Checking for clang prefix')
|
ctx.start_msg('Checking for clang prefix')
|
||||||
@ -120,20 +168,23 @@ def configure(ctx):
|
|||||||
|
|
||||||
includes = [ n.abspath() for n in [ prefix.find_node('include') ] if n ]
|
includes = [ n.abspath() for n in [ prefix.find_node('include') ] if n ]
|
||||||
libpath = [ n.abspath() for n in [ prefix.find_node(l) for l in ('lib', 'lib64')] if n ]
|
libpath = [ n.abspath() for n in [ prefix.find_node(l) for l in ('lib', 'lib64')] if n ]
|
||||||
ctx.check_cxx(msg='Checking for library clang', lib='clang', uselib_store='clang', includes=includes, libpath=libpath)
|
ctx.check_cxx(msg='Checking for library clang', lib=libname('clang'), uselib_store='clang', includes=includes, libpath=libpath)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
global CLANG_TARBALL_NAME
|
global CLANG_TARBALL_NAME
|
||||||
|
|
||||||
# TODO Remove these after dropping clang 4 (after we figure out how to index Chrome)
|
# TODO Remove these after dropping clang 4 (after we figure out how to index Chrome)
|
||||||
if ctx.options.bundled_clang[0] == '4':
|
if ctx.options.bundled_clang[0] == '4':
|
||||||
|
CLANG_TARBALL_EXT = '.tar.xz'
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
CLANG_TARBALL_NAME = 'clang+llvm-$version-x86_64-apple-darwin'
|
CLANG_TARBALL_NAME = 'clang+llvm-$version-x86_64-apple-darwin'
|
||||||
elif sys.platform.startswith('linux'):
|
elif sys.platform.startswith('linux'):
|
||||||
# These executable depend on libtinfo.so.5
|
# These executable depend on libtinfo.so.5
|
||||||
CLANG_TARBALL_NAME = 'clang+llvm-$version-x86_64-linux-gnu-ubuntu-14.04'
|
CLANG_TARBALL_NAME = 'clang+llvm-$version-x86_64-linux-gnu-ubuntu-14.04'
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
CLANG_TARBALL_NAME = 'LLVM-$version-win64'
|
||||||
|
CLANG_TARBALL_EXT = '.exe'
|
||||||
else:
|
else:
|
||||||
# TODO: windows support (it's an exe!)
|
|
||||||
sys.stderr.write('ERROR: Unknown platform {0}\n'.format(sys.platform))
|
sys.stderr.write('ERROR: Unknown platform {0}\n'.format(sys.platform))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -141,17 +192,21 @@ def configure(ctx):
|
|||||||
# Directory clang has been extracted to.
|
# Directory clang has been extracted to.
|
||||||
CLANG_DIRECTORY = '{0}/{1}'.format(out, CLANG_TARBALL_NAME)
|
CLANG_DIRECTORY = '{0}/{1}'.format(out, CLANG_TARBALL_NAME)
|
||||||
# URL of the tarball to download.
|
# URL of the tarball to download.
|
||||||
CLANG_TARBALL_URL = 'http://releases.llvm.org/{0}/{1}.tar.xz'.format(ctx.options.bundled_clang, CLANG_TARBALL_NAME)
|
CLANG_TARBALL_URL = 'http://releases.llvm.org/{0}/{1}{2}'.format(ctx.options.bundled_clang, CLANG_TARBALL_NAME, CLANG_TARBALL_EXT)
|
||||||
|
|
||||||
print('Checking for clang')
|
print('Checking for clang')
|
||||||
download_and_extract(CLANG_DIRECTORY, CLANG_TARBALL_URL)
|
download_and_extract(CLANG_DIRECTORY, CLANG_TARBALL_URL, CLANG_TARBALL_EXT)
|
||||||
bundled_clang_dir = os.path.join(out, ctx.options.variant, 'lib', CLANG_TARBALL_NAME)
|
bundled_clang_dir = os.path.join(out, ctx.options.variant, 'lib', CLANG_TARBALL_NAME)
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(bundled_clang_dir))
|
os.makedirs(os.path.dirname(bundled_clang_dir))
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
clang_dir = os.path.normpath('../../' + CLANG_TARBALL_NAME)
|
||||||
try:
|
try:
|
||||||
os.symlink('../../' + CLANG_TARBALL_NAME, bundled_clang_dir)
|
os.symlink(clang_dir, bundled_clang_dir, target_is_directory=True)
|
||||||
|
except NotImplementedError:
|
||||||
|
# Copying the whole directory instead.
|
||||||
|
shutil.copytree(clang_dir, bundled_clang_dir)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -159,33 +214,11 @@ def configure(ctx):
|
|||||||
ctx.check_cxx(uselib_store='clang',
|
ctx.check_cxx(uselib_store='clang',
|
||||||
includes=clang_node.find_dir('include').abspath(),
|
includes=clang_node.find_dir('include').abspath(),
|
||||||
libpath=clang_node.find_dir('lib').abspath(),
|
libpath=clang_node.find_dir('lib').abspath(),
|
||||||
lib='clang')
|
lib=libname('clang'))
|
||||||
|
|
||||||
ctx.msg('Clang includes', ctx.env.INCLUDES_clang)
|
ctx.msg('Clang includes', ctx.env.INCLUDES_clang)
|
||||||
ctx.msg('Clang library dir', ctx.env.LIBPATH_clang)
|
ctx.msg('Clang library dir', ctx.env.LIBPATH_clang)
|
||||||
|
|
||||||
"""
|
|
||||||
# Download and save the compressed tarball as |compressed_file_name|.
|
|
||||||
if not os.path.isfile(CLANG_TARBALL_LOCAL_PATH):
|
|
||||||
print('Downloading clang tarball')
|
|
||||||
print(' destination: {0}'.format(CLANG_TARBALL_LOCAL_PATH))
|
|
||||||
print(' source: {0}'.format(CLANG_TARBALL_URL))
|
|
||||||
# TODO: verify checksum
|
|
||||||
response = urlopen(CLANG_TARBALL_URL)
|
|
||||||
with open(CLANG_TARBALL_LOCAL_PATH, 'wb') as f:
|
|
||||||
f.write(response.read())
|
|
||||||
else:
|
|
||||||
print('Found clang tarball at {0}'.format(CLANG_TARBALL_LOCAL_PATH))
|
|
||||||
|
|
||||||
# Extract the tarball.
|
|
||||||
if not os.path.isdir(CLANG_DIRECTORY):
|
|
||||||
print('Extracting clang')
|
|
||||||
# TODO: make portable.
|
|
||||||
call(['tar', 'xf', CLANG_TARBALL_LOCAL_PATH, '-C', out])
|
|
||||||
else:
|
|
||||||
print('Found extracted clang at {0}'.format(CLANG_DIRECTORY))
|
|
||||||
"""
|
|
||||||
|
|
||||||
def build(bld):
|
def build(bld):
|
||||||
cc_files = bld.path.ant_glob(['src/*.cc', 'src/messages/*.cc'])
|
cc_files = bld.path.ant_glob(['src/*.cc', 'src/messages/*.cc'])
|
||||||
|
|
||||||
@ -199,11 +232,12 @@ def build(bld):
|
|||||||
|
|
||||||
clang_tarball_name = None
|
clang_tarball_name = None
|
||||||
# Fallback for windows
|
# Fallback for windows
|
||||||
default_resource_directory = os.path.join(os.getcwd(), 'resource_dir')
|
default_resource_directory = os.path.join(os.getcwd(), 'clang_resource_dir')
|
||||||
if bld.env['use_system_clang']:
|
if bld.env['use_system_clang']:
|
||||||
rpath = []
|
rpath = []
|
||||||
|
|
||||||
output = subprocess.check_output(['clang', '-###', '-xc', '/dev/null'], stderr=subprocess.STDOUT).decode()
|
devnull = '/dev/null' if sys.platform != 'win32' else 'NUL'
|
||||||
|
output = subprocess.check_output(['clang', '-###', '-xc', devnull], stderr=subprocess.STDOUT).decode()
|
||||||
match = re.search(r'"-resource-dir" "([^"]*)"', output, re.M | re.I)
|
match = re.search(r'"-resource-dir" "([^"]*)"', output, re.M | re.I)
|
||||||
if match:
|
if match:
|
||||||
default_resource_directory = match.group(1)
|
default_resource_directory = match.group(1)
|
||||||
@ -217,6 +251,16 @@ def build(bld):
|
|||||||
clang_tarball_name = os.path.basename(os.path.dirname(bld.env['LIBPATH_clang'][0]))
|
clang_tarball_name = os.path.basename(os.path.dirname(bld.env['LIBPATH_clang'][0]))
|
||||||
rpath = '@loader_path/../lib/' + clang_tarball_name + '/lib'
|
rpath = '@loader_path/../lib/' + clang_tarball_name + '/lib'
|
||||||
default_resource_directory = '../lib/{}/lib/clang/{}'.format(clang_tarball_name, bld.env['bundled_clang'])
|
default_resource_directory = '../lib/{}/lib/clang/{}'.format(clang_tarball_name, bld.env['bundled_clang'])
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
rpath = [] # Unsupported
|
||||||
|
name = os.path.basename(os.path.dirname(bld.env['LIBPATH_clang'][0]))
|
||||||
|
# Poor Windows users' RPATH
|
||||||
|
out_clang_dll = os.path.join(bld.path.get_bld().abspath(), 'bin', 'libclang.dll')
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(out_clang_dll))
|
||||||
|
os.symlink(os.path.join(bld.path.get_bld().abspath(), 'lib', name, 'bin', 'libclang.dll'), out_clang_dll)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
rpath = bld.env['LIBPATH_clang']
|
rpath = bld.env['LIBPATH_clang']
|
||||||
bld.program(
|
bld.program(
|
||||||
|
Loading…
Reference in New Issue
Block a user