mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 03:24:56 +08:00
122 lines
3.4 KiB
Python
122 lines
3.4 KiB
Python
#!/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:
|
|
Logs.warn('clang-format %d found, need >= %d, format command won\'t be available' % (major, CLANG_FORMAT_MIN_MAJOR))
|
|
conf.env.CLANG_FORMAT = []
|
|
return
|
|
|
|
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()
|