mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 03:24:56 +08:00
utils: add new tool xash-clang-format, a wrapper around clang-format that adds our formatting extensions
This commit is contained in:
113
.clang-format
Normal file
113
.clang-format
Normal file
@@ -0,0 +1,113 @@
|
||||
Language: Cpp
|
||||
Standard: c++17
|
||||
UseTab: ForContinuationAndIndentation
|
||||
TabWidth: 4
|
||||
IndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
IndentCaseLabels: false
|
||||
IndentPPDirectives: None
|
||||
NamespaceIndentation: None
|
||||
AccessModifierOffset: -4
|
||||
IndentAccessModifiers: false
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: Always
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
BeforeWhile: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
MaxEmptyLinesToKeep: 2
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
KeepEmptyLinesAtEOF: false
|
||||
SeparateDefinitionBlocks: Always
|
||||
InsertNewlineAtEOF: true
|
||||
LineEnding: LF
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
BreakBeforeBinaryOperators: All
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
PointerAlignment: Right
|
||||
DerivePointerAlignment: false
|
||||
ReferenceAlignment: Pointer
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpacesInParens: Custom
|
||||
SpacesInParensOptions:
|
||||
ExceptDoubleParentheses: true
|
||||
InCStyleCasts: false
|
||||
InConditionalStatements: true
|
||||
InEmptyParentheses: false
|
||||
Other: true
|
||||
# XashCollapseParens: true
|
||||
SpaceBeforeParens: Never
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInSquareBrackets: false
|
||||
Cpp11BracedListStyle: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
ColumnLimit: 0
|
||||
ReflowComments: false
|
||||
SortIncludes: Never
|
||||
SortUsingDeclarations: false
|
||||
InsertBraces: false
|
||||
RemoveBracesLLVM: false
|
||||
FixNamespaceComments: false
|
||||
119
scripts/waifulib/clang_format.py
Normal file
119
scripts/waifulib/clang_format.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
# clang_format - `./waf format` runs clang-format -i in parallel on opted-in source files.
|
||||
# Copyright (C) 2026 Xash3D FWGS contributors
|
||||
#
|
||||
# To override clang-format binary:
|
||||
# CLANG_FORMAT=xxx ./waf configure
|
||||
# CLANG_FORMAT=xxx ./waf format # for post-configure override, won't check for minimum version
|
||||
#
|
||||
# Opt task gens in:
|
||||
# bld.stlib(features='format', source='...', target='...', ...)
|
||||
|
||||
from waflib import Build, Logs, Task, TaskGen, Utils
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
# Enforce minimum major version
|
||||
CLANG_FORMAT_MIN_MAJOR = None
|
||||
|
||||
# File extensions to format
|
||||
CLANG_FORMAT_EXTS = ['.c', '.cpp', '.h']
|
||||
|
||||
def probe_major(binary):
|
||||
try:
|
||||
out = subprocess.check_output([binary, '--version'], stderr=subprocess.DEVNULL).decode('utf-8', 'replace')
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
return None
|
||||
|
||||
m = re.search(r'version\s+(\d+)', out)
|
||||
return int(m.group(1)) if m else None
|
||||
|
||||
def configure(conf):
|
||||
conf.find_program('clang-format', var='CLANG_FORMAT', mandatory=False)
|
||||
|
||||
if conf.env.CLANG_FORMAT and CLANG_FORMAT_MIN_MAJOR is not None:
|
||||
major = probe_major(conf.env.CLANG_FORMAT[0])
|
||||
|
||||
if major is None:
|
||||
Logs.warn('clang-format is not an executable or invalid')
|
||||
return
|
||||
|
||||
if major < CLANG_FORMAT_MIN_MAJOR:
|
||||
conf.fatal('clang-format %d found, need >= %d' % (major, CLANG_FORMAT_MIN_MAJOR))
|
||||
|
||||
conf.msg('Checking clang-format version', major)
|
||||
|
||||
class ClangFormatTask(Task.Task):
|
||||
color = 'PINK'
|
||||
always_run = True
|
||||
binary = 'clang-format' # just a default, will be overriden
|
||||
|
||||
def keyword(self):
|
||||
return 'Formatting'
|
||||
|
||||
def run(self):
|
||||
return self.exec_command([self.binary, '-i', self.inputs[0].abspath()], env=self.env.env or None)
|
||||
|
||||
# need this because if we add headers to to `source`
|
||||
# we can catch it in `format` feature, as nothing else
|
||||
# catches .h
|
||||
@TaskGen.extension('.h')
|
||||
def format_stub(self, node):
|
||||
pass
|
||||
|
||||
@TaskGen.feature('format')
|
||||
def make_format_tasks(self):
|
||||
if self.bld.cmd != 'format':
|
||||
return
|
||||
|
||||
for n in self.to_nodes(Utils.to_list(getattr(self, 'source', []))):
|
||||
if n.suffix() in CLANG_FORMAT_EXTS:
|
||||
self.create_task('ClangFormatTask', n)
|
||||
|
||||
self.source = [] # remove anything build related
|
||||
|
||||
class FormatContext(Build.BuildContext):
|
||||
cmd = 'format'
|
||||
fun = 'build'
|
||||
|
||||
def execute(self):
|
||||
self.restore()
|
||||
if not self.all_envs:
|
||||
self.load_envs()
|
||||
self.recurse([self.run_dir])
|
||||
|
||||
binary = os.environ.get('CLANG_FORMAT') or (self.env.CLANG_FORMAT and self.env.CLANG_FORMAT[0])
|
||||
if not binary:
|
||||
self.fatal('format: clang-format not configured')
|
||||
ClangFormatTask.binary = binary
|
||||
|
||||
# loops below filter taskgens by target name, format feature and by c-f task class
|
||||
wanted = set(self.targets.split(',')) if self.targets else None
|
||||
tasks = []
|
||||
for group in self.groups:
|
||||
for tg in group:
|
||||
if not isinstance(tg, TaskGen.task_gen):
|
||||
continue
|
||||
|
||||
if 'format' not in Utils.to_list(tg.features):
|
||||
continue
|
||||
|
||||
if wanted and getattr(tg, 'name', None) not in wanted:
|
||||
continue
|
||||
|
||||
tg.post()
|
||||
for t in tg.tasks:
|
||||
if isinstance(t, ClangFormatTask):
|
||||
tasks.append(t)
|
||||
|
||||
if not tasks:
|
||||
self.fatal("format: no sources matched (need features='format' on a task gen)")
|
||||
|
||||
Logs.info('format: %s on %d file(s)' % (binary, len(tasks)))
|
||||
self.progress_bar = 0 # FIXME: doesn't work with progress bar for some reason
|
||||
self.groups = [tasks]
|
||||
self.cur_group = 0
|
||||
self.targets = ''
|
||||
self.compile()
|
||||
91
utils/xash-clang-format/config.c
Normal file
91
utils/xash-clang-format/config.c
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
config.c - .clang-format extension parser
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "crtlib.h"
|
||||
#include "xcf.h"
|
||||
|
||||
#define XCF_KEY "XashCollapseParens"
|
||||
|
||||
/*
|
||||
============
|
||||
ConfigDefault
|
||||
============
|
||||
*/
|
||||
static void ConfigDefault( xcf_config_t *c )
|
||||
{
|
||||
c->collapse_parens = false;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
SkipWhitespace
|
||||
============
|
||||
*/
|
||||
static const char *SkipWhitespace( const char *p, const char *end )
|
||||
{
|
||||
for( ; p < end && ( *p == ' ' || *p == '\t' ); p++ )
|
||||
;
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
ParseConfigText
|
||||
|
||||
Parse extension keys out of a YAML text body.
|
||||
We only look at lines that begin (after optional whitespace) with `#`.
|
||||
============
|
||||
*/
|
||||
qboolean ParseConfigText( const char *text, xcf_config_t *cfg )
|
||||
{
|
||||
ConfigDefault( cfg );
|
||||
if( !text )
|
||||
return true;
|
||||
|
||||
for( const char *p = text; *p; )
|
||||
{
|
||||
const char *end = Q_strchrnul( p, '\n' );
|
||||
const char *q;
|
||||
const size_t klen = sizeof( XCF_KEY ) - 1;
|
||||
|
||||
q = SkipWhitespace( p, end );
|
||||
|
||||
if( q < end && *q == '#' )
|
||||
{
|
||||
q = SkipWhitespace( q + 1, end );
|
||||
|
||||
if( end - q >= klen && memcmp( q, XCF_KEY, klen ) == 0 )
|
||||
{
|
||||
q = SkipWhitespace( q + klen, end );
|
||||
|
||||
if( q < end && *q == ':' )
|
||||
{
|
||||
q = SkipWhitespace( q + 1, end );
|
||||
|
||||
if( end - q >= 4 && memcmp( q, "true", 4 ) == 0 )
|
||||
cfg->collapse_parens = true;
|
||||
else if( end - q >= 5 && memcmp( q, "false", 5 ) == 0 )
|
||||
cfg->collapse_parens = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( !*end )
|
||||
break;
|
||||
p = end + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
402
utils/xash-clang-format/main.c
Normal file
402
utils/xash-clang-format/main.c
Normal file
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
main.c - xash-clang-format wrapper entry point
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "crtlib.h"
|
||||
#include "xcf.h"
|
||||
|
||||
#ifndef XCF_CLANG_FORMAT_DEFAULT
|
||||
#define XCF_CLANG_FORMAT_DEFAULT "clang-format"
|
||||
#endif
|
||||
|
||||
// TODO: move to custom config keys
|
||||
#define XCF_MIN_CLANG_FORMAT_MAJOR 17
|
||||
|
||||
static const char *g_clang_format = NULL;
|
||||
|
||||
/*
|
||||
============
|
||||
ResolveClangFormat
|
||||
============
|
||||
*/
|
||||
static const char *ResolveClangFormat( void )
|
||||
{
|
||||
const char *e = getenv( "XASH_CLANG_FORMAT_BIN" );
|
||||
|
||||
if( !COM_StringEmptyOrNULL( e ))
|
||||
return e;
|
||||
|
||||
return XCF_CLANG_FORMAT_DEFAULT;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
IsFileArg
|
||||
|
||||
Attempt to distinguish files from other args.
|
||||
But to keep parsing args simple, it will parse value as --key value as
|
||||
filename. :(
|
||||
============
|
||||
*/
|
||||
static qboolean IsFileArg( const char *a )
|
||||
{
|
||||
if( COM_StringEmptyOrNULL( a ))
|
||||
return false;
|
||||
if( a[0] == '-' )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
ReadTheFile
|
||||
|
||||
============
|
||||
*/
|
||||
static qboolean ReadTheFile( const char *path, xcf_buf_t *out )
|
||||
{
|
||||
FILE *f = fopen( path, "rb" );
|
||||
|
||||
if( !f )
|
||||
return false;
|
||||
|
||||
char buf[8192];
|
||||
size_t n;
|
||||
|
||||
while(( n = fread( buf, 1, sizeof( buf ), f )) > 0 )
|
||||
{
|
||||
if( !BufPutMem( out, buf, n ))
|
||||
{
|
||||
fclose( f );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fclose( f );
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
WriteTheFile
|
||||
|
||||
============
|
||||
*/
|
||||
static qboolean WriteTheFile( const char *path, const char *data, size_t len )
|
||||
{
|
||||
FILE *f = fopen( path, "wb" );
|
||||
|
||||
if( !f )
|
||||
return false;
|
||||
if( fwrite( data, 1, len, f ) != len )
|
||||
{
|
||||
fclose( f );
|
||||
return false;
|
||||
}
|
||||
if( fclose( f ) != 0 )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
FindConfig
|
||||
============
|
||||
*/
|
||||
static char *FindConfig( const char *startdir )
|
||||
{
|
||||
char dir[MAX_SYSPATH];
|
||||
|
||||
if( startdir )
|
||||
{
|
||||
Q_strncpy( dir, startdir, sizeof( dir ));
|
||||
}
|
||||
else if( !getcwd( dir, sizeof( dir )))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
char path[MAX_SYSPATH];
|
||||
struct stat st;
|
||||
char *slash;
|
||||
|
||||
Q_snprintf( path, sizeof( path ), "%s/.clang-format", dir );
|
||||
|
||||
if( stat( path, &st ) == 0 && S_ISREG( st.st_mode ))
|
||||
{
|
||||
size_t len = Q_strlen( path ) + 1;
|
||||
char *r = malloc( len );
|
||||
|
||||
if( r )
|
||||
Q_strncpy( r, path, len );
|
||||
return r;
|
||||
}
|
||||
|
||||
slash = Q_strrchr( dir, '/' );
|
||||
if( !slash || slash == dir )
|
||||
break;
|
||||
*slash = 0;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
LoadConfig
|
||||
============
|
||||
*/
|
||||
static qboolean LoadConfig( const char *path, xcf_config_t *cfg )
|
||||
{
|
||||
xcf_buf_t buf = { 0 };
|
||||
qboolean ok = false;
|
||||
|
||||
if( path && ReadTheFile( path, &buf ) && BufPutChar( &buf, 0 ))
|
||||
ok = ParseConfigText( buf.data, cfg );
|
||||
else
|
||||
ParseConfigText( "", cfg );
|
||||
|
||||
BufFree( &buf );
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Emit
|
||||
|
||||
Transform pass on buffer and output to file
|
||||
============
|
||||
*/
|
||||
static qboolean Emit( const xcf_config_t *cfg, const xcf_buf_t *raw, FILE *dst, const char *write_path )
|
||||
{
|
||||
xcf_buf_t out = { 0 };
|
||||
const char *data;
|
||||
size_t len;
|
||||
if( cfg->collapse_parens && Transform( raw->data, raw->len, &out ))
|
||||
{
|
||||
data = out.data;
|
||||
len = out.len;
|
||||
}
|
||||
else
|
||||
{
|
||||
data = raw->data;
|
||||
len = raw->len;
|
||||
}
|
||||
|
||||
qboolean ok = true;
|
||||
if( write_path )
|
||||
{
|
||||
if( !WriteTheFile( write_path, data, len ))
|
||||
{
|
||||
fprintf( stderr, "xash-clang-format: cannot write %s: %s\n", write_path, strerror( errno ));
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
else if( len )
|
||||
{
|
||||
if( fwrite( data, 1, len, dst ) != len )
|
||||
ok = false;
|
||||
}
|
||||
|
||||
BufFree( &out );
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
PipeMode
|
||||
|
||||
Process stdout (when clang-format called without -i)
|
||||
============
|
||||
*/
|
||||
static int PipeMode( int argc, char **argv, const xcf_config_t *cfg )
|
||||
{
|
||||
char **fwd = malloc( sizeof( char * ) * ( argc + 1 ));
|
||||
if( !fwd )
|
||||
return 1;
|
||||
|
||||
fwd[0] = (char *)g_clang_format;
|
||||
for( int i = 1; i < argc; i++ )
|
||||
fwd[i] = argv[i];
|
||||
fwd[argc] = NULL;
|
||||
|
||||
xcf_buf_t raw = { 0 };
|
||||
int rc = RunCapture( fwd, &raw, false );
|
||||
free( fwd );
|
||||
|
||||
if( rc != 0 )
|
||||
{
|
||||
// pass through whatever clang-format produced
|
||||
if( raw.len )
|
||||
fwrite( raw.data, 1, raw.len, stdout );
|
||||
BufFree( &raw );
|
||||
return rc < 0 ? 1 : rc;
|
||||
}
|
||||
|
||||
rc = Emit( cfg, &raw, stdout, NULL ) ? 0 : 1;
|
||||
BufFree( &raw );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
PostProcessFile
|
||||
|
||||
Read `path`, run the paren-collapse pass, write result back.
|
||||
============
|
||||
*/
|
||||
static qboolean PostProcessFile( const char *path )
|
||||
{
|
||||
xcf_buf_t in = { 0 };
|
||||
xcf_buf_t out = { 0 };
|
||||
qboolean ok = false;
|
||||
|
||||
if( ReadTheFile( path, &in ) && Transform( in.data, in.len, &out ))
|
||||
ok = WriteTheFile( path, out.data, out.len );
|
||||
|
||||
BufFree( &in );
|
||||
BufFree( &out );
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
InPlaceMode
|
||||
|
||||
Post-process mode, when clang-format is called with -i
|
||||
============
|
||||
*/
|
||||
static int InPlaceMode( int argc, char **argv, const xcf_config_t *cfg )
|
||||
{
|
||||
char **fwd = malloc( sizeof( char * ) * ( argc + 1 ));
|
||||
|
||||
if( !fwd )
|
||||
return 1;
|
||||
|
||||
fwd[0] = (char *)g_clang_format;
|
||||
for( int i = 1; i < argc; i++ )
|
||||
fwd[i] = argv[i];
|
||||
fwd[argc] = NULL;
|
||||
|
||||
xcf_buf_t sink = { 0 };
|
||||
int rc = RunCapture( fwd, &sink, false );
|
||||
|
||||
BufFree( &sink );
|
||||
free( fwd );
|
||||
|
||||
if( rc != 0 )
|
||||
return rc < 0 ? 1 : rc;
|
||||
|
||||
if( !cfg->collapse_parens )
|
||||
return 0;
|
||||
|
||||
for( int i = 1; i < argc; i++ )
|
||||
{
|
||||
if( !IsFileArg( argv[i] ))
|
||||
continue;
|
||||
if( !PostProcessFile( argv[i] ))
|
||||
{
|
||||
fprintf( stderr, "xash-clang-format: post-process failed for %s\n", argv[i] );
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void Usage( FILE *dst )
|
||||
{
|
||||
fprintf( dst,
|
||||
"xash-clang-format: clang-format wrapper with xash3d-fwgs extensions\n"
|
||||
"\n"
|
||||
"Usage: xash-clang-format [clang-format options] [<file> ...]\n"
|
||||
" xash-clang-format -i <file> ...\n"
|
||||
"\n"
|
||||
"Most clang-format flags are forwarded as-is but there are limitations:\n"
|
||||
"* --key value must be passed as --key=value\n"
|
||||
"\n"
|
||||
"Use XASH_CLANG_FORMAT_BIN envvar to override the clang-format binary;\n"
|
||||
" setting it also skips the version probe.\n"
|
||||
"\n"
|
||||
"Wrapper's config is shared with clang-format config, but extended keys\n"
|
||||
" start with comment hash.\n"
|
||||
"New keys:\n"
|
||||
" XashCollapseParens, accepts 'true' or 'false'\n" );
|
||||
}
|
||||
|
||||
int main( int argc, char **argv )
|
||||
{
|
||||
g_clang_format = ResolveClangFormat();
|
||||
|
||||
qboolean has_inplace = false;
|
||||
qboolean has_version = false;
|
||||
qboolean has_help = false;
|
||||
|
||||
for( int i = 1; i < argc; i++ )
|
||||
{
|
||||
if( Q_strcmp( argv[i], "-i" ) == 0 )
|
||||
has_inplace = true;
|
||||
else if( Q_strcmp( argv[i], "--version" ) == 0 || Q_strcmp( argv[i], "-version" ) == 0 )
|
||||
has_version = true;
|
||||
else if( Q_strcmp( argv[i], "--help" ) == 0 || Q_strcmp( argv[i], "-h" ) == 0 || Q_strcmp( argv[i], "-help" ) == 0 )
|
||||
has_help = true;
|
||||
}
|
||||
|
||||
if( has_help )
|
||||
{
|
||||
Usage( stdout );
|
||||
return 0;
|
||||
}
|
||||
|
||||
if( has_version )
|
||||
{
|
||||
// transparent passthrough so tools that scrape `clang-format --version`
|
||||
// (e.g. git clang-format auto-detection) keep working
|
||||
execlp( g_clang_format, g_clang_format, "--version", (char *)NULL );
|
||||
fprintf( stderr, "xash-clang-format: cannot exec %s: %s\n", g_clang_format, strerror( errno ));
|
||||
return 127;
|
||||
}
|
||||
|
||||
// if caller sets XASH_CLANG_FORMAT_BIN — they own version compatibility
|
||||
// so skip the probe (lets ./waf format do the check once for parallel invocation)
|
||||
if( COM_StringEmptyOrNULL( getenv( "XASH_CLANG_FORMAT_BIN" )))
|
||||
{
|
||||
int major = 0;
|
||||
|
||||
if( !ClangFormatVersion( g_clang_format, &major ))
|
||||
{
|
||||
fprintf( stderr, "xash-clang-format: cannot probe clang-format version (binary: %s)\n", g_clang_format );
|
||||
return 2;
|
||||
}
|
||||
if( major < XCF_MIN_CLANG_FORMAT_MAJOR )
|
||||
{
|
||||
fprintf( stderr, "xash-clang-format: clang-format %d found, need >= %d\n",
|
||||
major, XCF_MIN_CLANG_FORMAT_MAJOR );
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
xcf_config_t cfg;
|
||||
char *cfgpath = FindConfig( NULL );
|
||||
LoadConfig( cfgpath, &cfg );
|
||||
free( cfgpath );
|
||||
|
||||
return has_inplace ? InPlaceMode( argc, argv, &cfg ) : PipeMode( argc, argv, &cfg );
|
||||
}
|
||||
99
utils/xash-clang-format/proc.c
Normal file
99
utils/xash-clang-format/proc.c
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
proc.c - subprocess utilities
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include "xcf.h"
|
||||
|
||||
/*
|
||||
============
|
||||
RunCapture
|
||||
|
||||
Spawn argv as a child, capture stdout into `out`.
|
||||
============
|
||||
*/
|
||||
int RunCapture( char *const argv[], xcf_buf_t *out, qboolean silent_stderr )
|
||||
{
|
||||
int p[2];
|
||||
pid_t pid;
|
||||
char buf[8192];
|
||||
int status;
|
||||
|
||||
if( pipe( p ) < 0 )
|
||||
return -1;
|
||||
|
||||
pid = fork();
|
||||
if( pid < 0 )
|
||||
{
|
||||
close( p[0] );
|
||||
close( p[1] );
|
||||
return -1;
|
||||
}
|
||||
|
||||
if( pid == 0 )
|
||||
{
|
||||
close( p[0] );
|
||||
dup2( p[1], STDOUT_FILENO );
|
||||
close( p[1] );
|
||||
if( silent_stderr )
|
||||
{
|
||||
int devnull = open( "/dev/null", O_WRONLY );
|
||||
|
||||
if( devnull >= 0 )
|
||||
{
|
||||
dup2( devnull, STDERR_FILENO );
|
||||
close( devnull );
|
||||
}
|
||||
}
|
||||
execvp( argv[0], argv );
|
||||
_exit( 127 );
|
||||
}
|
||||
|
||||
close( p[1] );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
ssize_t r = read( p[0], buf, sizeof( buf ));
|
||||
|
||||
if( r < 0 )
|
||||
{
|
||||
if( errno == EINTR )
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if( r == 0 )
|
||||
break;
|
||||
if( !BufPutMem( out, buf, r ))
|
||||
{
|
||||
close( p[0] );
|
||||
kill( pid, SIGKILL );
|
||||
waitpid( pid, NULL, 0 );
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
close( p[0] );
|
||||
|
||||
if( waitpid( pid, &status, 0 ) < 0 )
|
||||
return -1;
|
||||
|
||||
// heh wife excited
|
||||
if( WIFEXITED( status ))
|
||||
return WEXITSTATUS( status );
|
||||
|
||||
return -1;
|
||||
}
|
||||
77
utils/xash-clang-format/tests/test_config.c
Normal file
77
utils/xash-clang-format/tests/test_config.c
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <stdio.h>
|
||||
#include "xcf.h"
|
||||
|
||||
static int run_case( const char *label, const char *yaml, qboolean expect )
|
||||
{
|
||||
xcf_config_t cfg;
|
||||
|
||||
ParseConfigText( yaml, &cfg );
|
||||
|
||||
if( cfg.collapse_parens != expect )
|
||||
{
|
||||
fprintf( stderr, "[%s] collapse_parens=%d, expected %d\n yaml:\n%s\n",
|
||||
label, cfg.collapse_parens, expect, yaml );
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main( void )
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if( !run_case( "empty", "", false ))
|
||||
return ++n;
|
||||
|
||||
xcf_config_t cfg2;
|
||||
|
||||
ParseConfigText( NULL, &cfg2 );
|
||||
if( cfg2.collapse_parens )
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "explicit true",
|
||||
"# XashCollapseParens: true\n", true ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "explicit false",
|
||||
"# XashCollapseParens: false\n", false ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "leading ws",
|
||||
" # XashCollapseParens: true\n", true ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "tabs",
|
||||
"#\tXashCollapseParens\t:\ttrue\n", true ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "unrelated key",
|
||||
"# SomeOtherKey: true\nIndentWidth: 4\n", false ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "in larger config",
|
||||
"---\n"
|
||||
"Language: Cpp\n"
|
||||
"# XashCollapseParens: true\n"
|
||||
"IndentWidth: 4\n",
|
||||
true ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "junk value",
|
||||
"# XashCollapseParens: maybe\n", false ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "no colon",
|
||||
"# XashCollapseParens true\n", false ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "last wins",
|
||||
"# XashCollapseParens: false\n# XashCollapseParens: true\n", true ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "no trailing nl",
|
||||
"# XashCollapseParens: true", true ))
|
||||
return ++n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
171
utils/xash-clang-format/tests/test_transform.c
Normal file
171
utils/xash-clang-format/tests/test_transform.c
Normal file
@@ -0,0 +1,171 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "xcf.h"
|
||||
|
||||
static int run_case( const char *label, const char *in, const char *expect )
|
||||
{
|
||||
xcf_buf_t out = { 0 };
|
||||
int ok;
|
||||
|
||||
if( !Transform( in, strlen( in ), &out ))
|
||||
{
|
||||
fprintf( stderr, "[%s] Transform returned error\n", label );
|
||||
BufFree( &out );
|
||||
return 0;
|
||||
}
|
||||
|
||||
ok = ( out.len == strlen( expect ) && memcmp( out.data, expect, out.len ) == 0 );
|
||||
|
||||
if( !ok )
|
||||
{
|
||||
fprintf( stderr, "[%s] mismatch\n in: %s\n out: %.*s\n expect: %s\n",
|
||||
label, in, (int)out.len, out.data, expect );
|
||||
}
|
||||
|
||||
BufFree( &out );
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main( void )
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if( !run_case( "outer parens",
|
||||
"if( ( a + b ) )\n",
|
||||
"if(( a + b ))\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "triple nesting",
|
||||
"x = ( ( ( a ) ) );\n",
|
||||
"x = ((( a )));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "mixed [ )",
|
||||
"arr[i] )\n",
|
||||
"arr[i] )\n" ))
|
||||
return ++n;
|
||||
if( !run_case( "mixed ) ]",
|
||||
"f( a ) ];\n",
|
||||
"f( a ) ];\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "newline boundary",
|
||||
"(\n(\n",
|
||||
"(\n(\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "string literal",
|
||||
"x = \"( ( hello ) )\";\n",
|
||||
"x = \"( ( hello ) )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "escaped quote in string",
|
||||
"x = \"a \\\" ( ( b ) ) \";\n",
|
||||
"x = \"a \\\" ( ( b ) ) \";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "line comment",
|
||||
"a; // ( ( comment ) )\nb;\n",
|
||||
"a; // ( ( comment ) )\nb;\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "block comment",
|
||||
"/* ( ( leave\nthis ) ) alone */ x;\n",
|
||||
"/* ( ( leave\nthis ) ) alone */ x;\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "char literal '('",
|
||||
"if( c == '(' )\n",
|
||||
"if( c == '(' )\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "raw string",
|
||||
"const char *s = R\"x(( (\nstays )x\";\n",
|
||||
"const char *s = R\"x(( (\nstays )x\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "raw string empty delim",
|
||||
"R\"(( (\n))\"",
|
||||
"R\"(( (\n))\"" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "ident Rabbit not raw",
|
||||
"int Rabbit = ( ( 1 ) );\n",
|
||||
"int Rabbit = (( 1 ));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "u8R raw",
|
||||
"const auto s = u8R\"(( ( )\";\n",
|
||||
"const auto s = u8R\"(( ( )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "include angle",
|
||||
"#include <foo.h>\nf( ( x ) );\n",
|
||||
"#include <foo.h>\nf(( x ));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "tabs between parens",
|
||||
"f(\t(\tx\t)\t);\n",
|
||||
"f((\tx\t));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "empty", "", "" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "idempotent",
|
||||
"f((( x )));\n",
|
||||
"f((( x )));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "already touching",
|
||||
"((a))",
|
||||
"((a))" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "single paren",
|
||||
"f( a, b )",
|
||||
"f( a, b )" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "multiple pairs",
|
||||
"f( ( a ) ) + g( ( b ) );\n",
|
||||
"f(( a )) + g(( b ));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "L wide string",
|
||||
"wchar_t *s = L\"( ( x ) )\";\n",
|
||||
"wchar_t *s = L\"( ( x ) )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "u utf16 string",
|
||||
"auto s = u\"( ( x ) )\";\n",
|
||||
"auto s = u\"( ( x ) )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "U utf32 string",
|
||||
"auto s = U\"( ( x ) )\";\n",
|
||||
"auto s = U\"( ( x ) )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "u8 utf8 string",
|
||||
"auto s = u8\"( ( x ) )\";\n",
|
||||
"auto s = u8\"( ( x ) )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "L wide char",
|
||||
"if( c == L'(' )\n",
|
||||
"if( c == L'(' )\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "u8 char",
|
||||
"if( c == u8'(' )\n",
|
||||
"if( c == u8'(' )\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "ident L not prefix",
|
||||
"int callL = ( ( 1 ) );\n",
|
||||
"int callL = (( 1 ));\n" ))
|
||||
return ++n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
58
utils/xash-clang-format/tests/test_version.c
Normal file
58
utils/xash-clang-format/tests/test_version.c
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <stdio.h>
|
||||
#include "xcf.h"
|
||||
|
||||
static int run_case( const char *label, const char *s, qboolean expect_ok, int expect_major )
|
||||
{
|
||||
int major = -1;
|
||||
qboolean ok = ParseVersion( s, &major );
|
||||
|
||||
if( ok != expect_ok )
|
||||
{
|
||||
fprintf( stderr, "[%s] ok=%d expected %d (input: %s)\n", label, ok, expect_ok, s ? s : "(null)" );
|
||||
return 0;
|
||||
}
|
||||
if( ok && major != expect_major )
|
||||
{
|
||||
fprintf( stderr, "[%s] major=%d expected %d (input: %s)\n", label, major, expect_major, s );
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main( void )
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if( !run_case( "vanilla 17",
|
||||
"clang-format version 17.0.6 (Fedora 17.0.6-1.fc40)\n", true, 17 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "ubuntu 18",
|
||||
"Ubuntu clang-format version 18.1.3 (1ubuntu1)\n", true, 18 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "large major",
|
||||
"clang-format version 100.0.0\n", true, 100 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "garbage",
|
||||
"this has no version here\n", false, 0 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "null", NULL, false, 0 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "distro tag with digits",
|
||||
"Ubuntu99 clang-format version 17.0.6\n", true, 17 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "bare digits",
|
||||
"foo 42 bar\n", false, 0 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "no version word",
|
||||
"fake clang-format 19.1.0\n", false, 0 ))
|
||||
return ++n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
325
utils/xash-clang-format/transform.c
Normal file
325
utils/xash-clang-format/transform.c
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
transform.c - source post-processing pass
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "xcf.h"
|
||||
|
||||
#define XCF_RAW_DELIM_MAX 16 // C++11: 16 chars max in raw-string delimiter
|
||||
|
||||
/*
|
||||
============
|
||||
BufFree
|
||||
|
||||
============
|
||||
*/
|
||||
void BufFree( xcf_buf_t *b )
|
||||
{
|
||||
free( b->data );
|
||||
b->data = NULL;
|
||||
b->len = b->cap = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
BufGrow
|
||||
|
||||
============
|
||||
*/
|
||||
static qboolean BufGrow( xcf_buf_t *b, size_t need )
|
||||
{
|
||||
size_t cap;
|
||||
char *p;
|
||||
|
||||
if( b->len + need <= b->cap )
|
||||
return true;
|
||||
|
||||
cap = b->cap ? b->cap : 256;
|
||||
while( cap < b->len + need )
|
||||
cap *= 2;
|
||||
|
||||
p = realloc( b->data, cap );
|
||||
if( !p )
|
||||
return false;
|
||||
|
||||
b->data = p;
|
||||
b->cap = cap;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
BufPutChar
|
||||
|
||||
============
|
||||
*/
|
||||
qboolean BufPutChar( xcf_buf_t *b, char c )
|
||||
{
|
||||
if( !BufGrow( b, 1 ))
|
||||
return false;
|
||||
b->data[b->len++] = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
BufPutMem
|
||||
|
||||
============
|
||||
*/
|
||||
qboolean BufPutMem( xcf_buf_t *b, const char *src, size_t n )
|
||||
{
|
||||
if( !n )
|
||||
return true;
|
||||
if( !BufGrow( b, n ))
|
||||
return false;
|
||||
memcpy( b->data + b->len, src, n );
|
||||
b->len += n;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
TryCollapse
|
||||
|
||||
Collapse spaces between the same type parentheses
|
||||
============
|
||||
*/
|
||||
static void TryCollapse( xcf_buf_t *out, char paren )
|
||||
{
|
||||
size_t i = out->len;
|
||||
|
||||
while( i > 0 && ( out->data[i - 1] == ' ' || out->data[i - 1] == '\t' ))
|
||||
i--;
|
||||
|
||||
if( i == 0 || i == out->len )
|
||||
return; // nothing to collapse, or no whitespace between
|
||||
|
||||
if( out->data[i - 1] != paren )
|
||||
return; // different bracket types or different char entirely
|
||||
|
||||
out->len = i;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
DetectRaw
|
||||
|
||||
Detect C++11 raw string because why not
|
||||
============
|
||||
*/
|
||||
static qboolean DetectRaw( const char *s, size_t len, size_t i, size_t *prefix_end, size_t *paren )
|
||||
{
|
||||
size_t j = i, k;
|
||||
|
||||
// must not be in the middle of an identifier
|
||||
if( i > 0 && ( isalnum( s[i - 1] ) || s[i - 1] == '_' ))
|
||||
return false;
|
||||
|
||||
if( j + 2 < len && s[j] == 'u' && s[j + 1] == '8' && s[j + 2] == 'R' )
|
||||
j += 3;
|
||||
else if( j + 1 < len && ( s[j] == 'u' || s[j] == 'U' || s[j] == 'L' ) && s[j + 1] == 'R' )
|
||||
j += 2;
|
||||
else if( j < len && s[j] == 'R' )
|
||||
j += 1;
|
||||
else
|
||||
return false;
|
||||
|
||||
if( j >= len || s[j] != '"' )
|
||||
return false;
|
||||
j++; // past the opening quote
|
||||
*prefix_end = j;
|
||||
|
||||
k = j;
|
||||
while( k < len && s[k] != '(' && k - j < XCF_RAW_DELIM_MAX )
|
||||
k++;
|
||||
|
||||
if( k >= len || s[k] != '(' )
|
||||
return false;
|
||||
|
||||
*paren = k;
|
||||
return true;
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
S_NORMAL,
|
||||
S_LINE_CMT,
|
||||
S_BLOCK_CMT,
|
||||
S_STRING,
|
||||
S_CHAR,
|
||||
S_RAW,
|
||||
};
|
||||
|
||||
/*
|
||||
============
|
||||
Transform
|
||||
|
||||
Parse source, find parens and try collapse them
|
||||
============
|
||||
*/
|
||||
qboolean Transform( const char *in, size_t in_len, xcf_buf_t *out )
|
||||
{
|
||||
size_t raw_dlen = 0;
|
||||
char raw_delim[XCF_RAW_DELIM_MAX + 1];
|
||||
int state = S_NORMAL;
|
||||
|
||||
for( size_t i = 0; i < in_len; i++ )
|
||||
{
|
||||
char c1 = in[i];
|
||||
char c2 = ( i + 1 < in_len ) ? in[i + 1] : 0;
|
||||
|
||||
switch( state )
|
||||
{
|
||||
case S_NORMAL:
|
||||
{
|
||||
size_t pe, pp;
|
||||
|
||||
if( c1 == '/' && c2 == '/' )
|
||||
{
|
||||
if( !BufPutMem( out, in + i, 2 ))
|
||||
return false;
|
||||
i++;
|
||||
state = S_LINE_CMT;
|
||||
break;
|
||||
}
|
||||
|
||||
if( c1 == '/' && c2 == '*' )
|
||||
{
|
||||
if( !BufPutMem( out, in + i, 2 ))
|
||||
return false;
|
||||
i++;
|
||||
state = S_BLOCK_CMT;
|
||||
break;
|
||||
}
|
||||
|
||||
if( DetectRaw( in, in_len, i, &pe, &pp ))
|
||||
{
|
||||
size_t dl = pp - pe;
|
||||
|
||||
if( dl > XCF_RAW_DELIM_MAX )
|
||||
dl = XCF_RAW_DELIM_MAX;
|
||||
|
||||
if( !BufPutMem( out, in + i, pp - i + 1 ))
|
||||
return false;
|
||||
memcpy( raw_delim, in + pe, dl );
|
||||
raw_delim[dl] = 0;
|
||||
raw_dlen = dl;
|
||||
i = pp; // sit on the '('
|
||||
state = S_RAW;
|
||||
break;
|
||||
}
|
||||
|
||||
if( c1 == '"' )
|
||||
{
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
state = S_STRING;
|
||||
break;
|
||||
}
|
||||
|
||||
if( c1 == '\'' )
|
||||
{
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
state = S_CHAR;
|
||||
break;
|
||||
}
|
||||
|
||||
if( c1 == '(' || c1 == ')' )
|
||||
TryCollapse( out, c1 );
|
||||
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
case S_LINE_CMT:
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
if( c1 == '\n' )
|
||||
state = S_NORMAL;
|
||||
break;
|
||||
|
||||
case S_BLOCK_CMT:
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
if( c1 == '*' && c2 == '/' )
|
||||
{
|
||||
if( !BufPutChar( out, c2 ))
|
||||
return false;
|
||||
i++;
|
||||
state = S_NORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_STRING:
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
if( c1 == '\\' && c2 )
|
||||
{
|
||||
if( !BufPutChar( out, c2 ))
|
||||
return false;
|
||||
i++;
|
||||
}
|
||||
else if( c1 == '"' )
|
||||
{
|
||||
state = S_NORMAL;
|
||||
}
|
||||
else if( c1 == '\n' )
|
||||
{
|
||||
// unterminated; recover at next line
|
||||
state = S_NORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_CHAR:
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
if( c1 == '\\' && c2 )
|
||||
{
|
||||
if( !BufPutChar( out, c2 ))
|
||||
return false;
|
||||
i++;
|
||||
}
|
||||
else if( c1 == '\'' )
|
||||
{
|
||||
state = S_NORMAL;
|
||||
}
|
||||
else if( c1 == '\n' )
|
||||
{
|
||||
state = S_NORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_RAW:
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
if( c1 == ')'
|
||||
&& i + 1 + raw_dlen + 1 <= in_len
|
||||
&& memcmp( in + i + 1, raw_delim, raw_dlen ) == 0
|
||||
&& in[i + 1 + raw_dlen] == '"' )
|
||||
{
|
||||
if( !BufPutMem( out, in + i + 1, raw_dlen + 1 ))
|
||||
return false;
|
||||
i += raw_dlen + 1;
|
||||
state = S_NORMAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
67
utils/xash-clang-format/version.c
Normal file
67
utils/xash-clang-format/version.c
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
version.c - clang-format version probe
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#include "crtlib.h"
|
||||
#include "xcf.h"
|
||||
|
||||
/*
|
||||
============
|
||||
ParseVersion
|
||||
|
||||
Parse the major version number out of a `clang-format --version`
|
||||
============
|
||||
*/
|
||||
qboolean ParseVersion( const char *s, int *major )
|
||||
{
|
||||
const char *p;
|
||||
|
||||
if( COM_StringEmptyOrNULL( s ) || !major )
|
||||
return false;
|
||||
|
||||
// anchor on "version "
|
||||
p = Q_strstr( s, "version " );
|
||||
if( !p )
|
||||
return false;
|
||||
p += sizeof( "version " ) - 1;
|
||||
|
||||
while( *p == ' ' || *p == '\t' )
|
||||
p++;
|
||||
if( *p < '0' || *p > '9' )
|
||||
return false;
|
||||
|
||||
*major = Q_atoi( p );
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
ClangFormatVersion
|
||||
|
||||
============
|
||||
*/
|
||||
qboolean ClangFormatVersion( const char *exe, int *major )
|
||||
{
|
||||
char *argv[] = { (char *)exe, (char *)"--version", NULL };
|
||||
xcf_buf_t out = { 0 };
|
||||
qboolean ok = false;
|
||||
|
||||
if( !exe || !major )
|
||||
return false;
|
||||
|
||||
if( RunCapture( argv, &out, true ) == 0 && BufPutChar( &out, 0 ))
|
||||
ok = ParseVersion( out.data, major );
|
||||
|
||||
BufFree( &out );
|
||||
return ok;
|
||||
}
|
||||
42
utils/xash-clang-format/wscript
Normal file
42
utils/xash-clang-format/wscript
Normal file
@@ -0,0 +1,42 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
def options(opt):
|
||||
grp = opt.get_option_group('Utilities options')
|
||||
|
||||
grp.add_option('--disable-utils-xcf', action='store_true',
|
||||
dest='DISABLE_UTILS_XCF', default = False,
|
||||
help='disable xash-clang-format wrapper utility [default: %(default)s]')
|
||||
|
||||
def configure(conf):
|
||||
# wrapper relies on POSIX, disable on Windows for now
|
||||
conf.env.DISABLE_UTILS_XCF = conf.options.DISABLE_UTILS_XCF or conf.env.DEST_OS == 'win32'
|
||||
|
||||
def build(bld):
|
||||
if bld.env.DISABLE_UTILS_XCF:
|
||||
return
|
||||
|
||||
bld.stlib(source = bld.path.ant_glob(['*.c', '*.h'], excl='main.c'),
|
||||
target = 'xcflib',
|
||||
features = 'format',
|
||||
includes = '.',
|
||||
use = 'public werror',
|
||||
install_path = None)
|
||||
|
||||
bld.program(source = 'main.c',
|
||||
target = 'xash-clang-format',
|
||||
features = 'format',
|
||||
use = 'xcflib werror',
|
||||
install_path = bld.env.BINDIR,
|
||||
subsystem = bld.env.CONSOLE_SUBSYSTEM)
|
||||
|
||||
if bld.env.TESTS:
|
||||
tests = ['transform', 'config', 'version']
|
||||
|
||||
for name in tests:
|
||||
bld.program(features = 'test format',
|
||||
source = 'tests/test_%s.c' % name,
|
||||
target = 'test_xcflib_%s' % name,
|
||||
includes = '.',
|
||||
use = 'xcflib werror',
|
||||
install_path = None)
|
||||
57
utils/xash-clang-format/xcf.h
Normal file
57
utils/xash-clang-format/xcf.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
xcf.h - clang-format wrapper internals
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#ifndef XCF_H
|
||||
#define XCF_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "xash3d_types.h"
|
||||
|
||||
//
|
||||
// transform.c
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
char *data;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} xcf_buf_t;
|
||||
|
||||
void BufFree( xcf_buf_t *b );
|
||||
qboolean BufPutChar( xcf_buf_t *b, char c );
|
||||
qboolean BufPutMem( xcf_buf_t *b, const char *src, size_t n );
|
||||
qboolean Transform( const char *in, size_t in_len, xcf_buf_t *out );
|
||||
|
||||
//
|
||||
// config.c
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
qboolean collapse_parens; // default: false
|
||||
} xcf_config_t;
|
||||
|
||||
qboolean ParseConfigText( const char *text, xcf_config_t *cfg );
|
||||
|
||||
//
|
||||
// version.c
|
||||
//
|
||||
qboolean ParseVersion( const char *s, int *major );
|
||||
qboolean ClangFormatVersion( const char *exe, int *major );
|
||||
|
||||
//
|
||||
// proc.c
|
||||
//
|
||||
int RunCapture( char *const argv[], xcf_buf_t *out, qboolean silent_stderr );
|
||||
|
||||
#endif // XCF_H
|
||||
25
wscript
25
wscript
@@ -4,9 +4,13 @@
|
||||
|
||||
from waflib import Build, Context, Logs, TaskGen
|
||||
from waflib.Tools import waf_unit_test, c_tests
|
||||
import clang_format
|
||||
import sys
|
||||
import os
|
||||
|
||||
# we need version 17 minimum
|
||||
clang_format.CLANG_FORMAT_MIN_MAJOR = 17
|
||||
|
||||
VERSION = '0.99'
|
||||
APPNAME = 'xash3d-fwgs'
|
||||
top = '.'
|
||||
@@ -32,6 +36,24 @@ def apply_subsystem_msvc(self):
|
||||
if 'test' in self.features:
|
||||
self.subsystem = self.env.CONSOLE_SUBSYSTEM
|
||||
|
||||
@TaskGen.feature('format')
|
||||
@TaskGen.after_method('make_format_tasks')
|
||||
def use_xash_clang_format(self):
|
||||
if self.bld.cmd != 'format':
|
||||
return
|
||||
|
||||
wrapper = self.bld.bldnode.find_node('utils/xash-clang-format/xash-clang-format')
|
||||
if not wrapper:
|
||||
self.bld.fatal('no xash-clang-format found in build directory, reconfigure and rebuild with --enable-utils')
|
||||
return
|
||||
|
||||
real_cf = clang_format.ClangFormatTask.binary
|
||||
for t in self.tasks:
|
||||
if isinstance(t, clang_format.ClangFormatTask):
|
||||
t.binary = wrapper.abspath()
|
||||
t.env.env = dict(os.environ, XASH_CLANG_FORMAT_BIN=real_cf)
|
||||
|
||||
|
||||
class Subproject:
|
||||
def __init__(self, name, fnFilter = None):
|
||||
self.name = name
|
||||
@@ -115,6 +137,7 @@ SUBDIRS = [
|
||||
# enabled optionally
|
||||
Subproject('utils/mdldec', lambda x: x.env.ENABLE_UTILS),
|
||||
Subproject('utils/xar', lambda x: x.env.ENABLE_UTILS and x.env.ENABLE_XAR),
|
||||
Subproject('utils/xash-clang-format', lambda x: x.env.ENABLE_UTILS),
|
||||
|
||||
# enabled on PSVita only
|
||||
Subproject('ref/gl/vgl_shim', lambda x: x.env.DEST_OS == 'psvita'),
|
||||
@@ -236,7 +259,7 @@ def configure(conf):
|
||||
if conf.env.COMPILER_CC == 'msvc':
|
||||
conf.load('msvc_pdb')
|
||||
|
||||
conf.load('msvs subproject clang_compilation_database strip_on_install waf_unit_test enforce_pic force_32bit ninja')
|
||||
conf.load('msvs subproject clang_compilation_database strip_on_install waf_unit_test enforce_pic force_32bit ninja clang_format')
|
||||
|
||||
conf.env.MSVC_SUBSYSTEM = 'WINDOWS'
|
||||
conf.env.CONSOLE_SUBSYSTEM = 'CONSOLE'
|
||||
|
||||
Reference in New Issue
Block a user