primer commit
This commit is contained in:
8
tools/linter/com_usar_clang-tidy.txt
Normal file
8
tools/linter/com_usar_clang-tidy.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# Per a un fitxer, desde l'arrel del projecte executar:
|
||||
clang-tidy source/fitxer.cpp -p build/ --fix
|
||||
|
||||
# Per a varios fitxers, desde l'arrel:
|
||||
find source/ \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) | \
|
||||
xargs -P4 -I{} bash -c 'echo "Procesando: {}"; clang-tidy {} -p build/ --fix'
|
||||
|
||||
|
||||
2
tools/linter/cppcheck_suppressions
Normal file
2
tools/linter/cppcheck_suppressions
Normal file
@@ -0,0 +1,2 @@
|
||||
*:/home/sergio/gitea/jaildoctors_dilemma/source/external/*
|
||||
*:/usr/include/*
|
||||
16
tools/linter/generate_compile_commands_json.sh
Normal file
16
tools/linter/generate_compile_commands_json.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🏁 Auto-detectar ruta base del proyecto
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# 📁 Ruta al build
|
||||
BUILD_DIR="$PROJECT_ROOT/build"
|
||||
|
||||
# 📄 Archivo de mapping personalizado
|
||||
MAPPING_FILE="$SCRIPT_DIR/sdl3_mapping.imp"
|
||||
|
||||
# 📦 Generar compile_commands.json
|
||||
echo "🔧 Generando compile_commands.json..."
|
||||
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -S "$PROJECT_ROOT" -B "$BUILD_DIR"
|
||||
|
||||
553
tools/linter/iwyu_tool.py
Normal file
553
tools/linter/iwyu_tool.py
Normal file
@@ -0,0 +1,553 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
##===--- iwyu_tool.py -----------------------------------------------------===##
|
||||
#
|
||||
# The LLVM Compiler Infrastructure
|
||||
#
|
||||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
#
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
""" Driver to consume a Clang compilation database and invoke IWYU.
|
||||
|
||||
Example usage with CMake:
|
||||
|
||||
# Unix systems
|
||||
$ mkdir build && cd build
|
||||
$ CC="clang" CXX="clang++" cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ...
|
||||
$ iwyu_tool.py -p .
|
||||
|
||||
# Windows systems
|
||||
$ mkdir build && cd build
|
||||
$ cmake -DCMAKE_CXX_COMPILER="%VCINSTALLDIR%/bin/cl.exe" \
|
||||
-DCMAKE_C_COMPILER="%VCINSTALLDIR%/VC/bin/cl.exe" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-G Ninja ...
|
||||
$ python3 iwyu_tool.py -p .
|
||||
|
||||
See iwyu_tool.py -h for more details on command-line arguments.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import shlex
|
||||
import shutil
|
||||
import argparse
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
|
||||
CORRECT_RE = re.compile(r'^\((.*?) has correct #includes/fwd-decls\)$')
|
||||
SHOULD_ADD_RE = re.compile(r'^(.*?) should add these lines:$')
|
||||
ADD_RE = re.compile('^(.*?) +// (.*)$')
|
||||
SHOULD_REMOVE_RE = re.compile(r'^(.*?) should remove these lines:$')
|
||||
FULL_LIST_RE = re.compile(r'The full include-list for (.*?):$')
|
||||
END_RE = re.compile(r'^---$')
|
||||
LINES_RE = re.compile(r'^- (.*?) // lines ([0-9]+)-[0-9]+$')
|
||||
|
||||
|
||||
GENERAL, ADD, REMOVE, LIST = range(4)
|
||||
|
||||
|
||||
def clang_formatter(output, style):
|
||||
""" Process iwyu's output into something clang-like. """
|
||||
formatted = []
|
||||
|
||||
state = (GENERAL, None)
|
||||
for line in output.splitlines():
|
||||
match = CORRECT_RE.match(line)
|
||||
if match:
|
||||
# See PR#1806 for more info
|
||||
continue
|
||||
match = SHOULD_ADD_RE.match(line)
|
||||
if match:
|
||||
state = (ADD, match.group(1))
|
||||
continue
|
||||
match = SHOULD_REMOVE_RE.match(line)
|
||||
if match:
|
||||
state = (REMOVE, match.group(1))
|
||||
continue
|
||||
match = FULL_LIST_RE.match(line)
|
||||
if match:
|
||||
state = (LIST, match.group(1))
|
||||
elif END_RE.match(line):
|
||||
state = (GENERAL, None)
|
||||
elif not line.strip():
|
||||
continue
|
||||
elif state[0] == GENERAL:
|
||||
formatted.append(line)
|
||||
elif state[0] == ADD:
|
||||
match = ADD_RE.match(line)
|
||||
if match:
|
||||
formatted.append("%s:1:1: %s: add '%s' (%s)" %
|
||||
(state[1],
|
||||
style,
|
||||
match.group(1),
|
||||
match.group(2)))
|
||||
else:
|
||||
formatted.append("%s:1:1: %s: add '%s'" %
|
||||
(state[1], style, line))
|
||||
elif state[0] == REMOVE:
|
||||
match = LINES_RE.match(line)
|
||||
line_no = match.group(2) if match else '1'
|
||||
formatted.append("%s:%s:1: %s: superfluous '%s'" %
|
||||
(state[1], line_no, style, match.group(1)))
|
||||
|
||||
return os.linesep.join(formatted)
|
||||
|
||||
|
||||
DEFAULT_FORMAT = 'iwyu'
|
||||
FORMATTERS = {
|
||||
'iwyu': lambda output: output,
|
||||
'clang': lambda output: clang_formatter(output, style="error"),
|
||||
'clang-warning': lambda output: clang_formatter(output, style="warning"),
|
||||
}
|
||||
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
# Case-insensitive match on Windows
|
||||
def normcase(s):
|
||||
return s.lower()
|
||||
else:
|
||||
def normcase(s):
|
||||
return s
|
||||
|
||||
|
||||
def is_subpath_of(path, parent):
|
||||
""" Return True if path is equal to or fully contained within parent.
|
||||
|
||||
Assumes both paths are canonicalized with os.path.realpath.
|
||||
"""
|
||||
parent = normcase(parent)
|
||||
path = normcase(path)
|
||||
|
||||
if path == parent:
|
||||
return True
|
||||
|
||||
if not path.startswith(parent):
|
||||
return False
|
||||
|
||||
# Now we know parent is a prefix of path, but they only share lineage if the
|
||||
# difference between them starts with a path separator, e.g. /a/b/c/file
|
||||
# is not a parent of /a/b/c/file.cpp, but /a/b/c and /a/b/c/ are.
|
||||
parent = parent.rstrip(os.path.sep)
|
||||
suffix = path[len(parent):]
|
||||
return suffix.startswith(os.path.sep)
|
||||
|
||||
|
||||
def is_msvc_driver(compile_command):
|
||||
""" Return True if compile_command matches an MSVC CL-style driver. """
|
||||
compile_command = normcase(compile_command)
|
||||
|
||||
if compile_command.endswith('cl.exe'):
|
||||
# Native MSVC compiler or clang-cl.exe
|
||||
return True
|
||||
|
||||
if compile_command.endswith('clang-cl'):
|
||||
# Cross clang-cl on non-Windows
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def win_split(cmdline):
|
||||
""" Minimal implementation of shlex.split for Windows following
|
||||
https://msdn.microsoft.com/en-us/library/windows/desktop/17w5ykft.aspx.
|
||||
"""
|
||||
def split_iter(cmdline):
|
||||
in_quotes = False
|
||||
backslashes = 0
|
||||
arg = ''
|
||||
for c in cmdline:
|
||||
if c == '\\':
|
||||
# MSDN: Backslashes are interpreted literally, unless they
|
||||
# immediately precede a double quotation mark.
|
||||
# Buffer them until we know what comes next.
|
||||
backslashes += 1
|
||||
elif c == '"':
|
||||
# Quotes can either be an escaped quote or the start of a quoted
|
||||
# string. Paraphrasing MSDN:
|
||||
# Before quotes, place one backslash in the arg for every pair
|
||||
# of leading backslashes. If the number of backslashes is odd,
|
||||
# retain the double quotation mark, otherwise interpret it as a
|
||||
# string delimiter and switch state.
|
||||
arg += '\\' * (backslashes // 2)
|
||||
if backslashes % 2 == 1:
|
||||
arg += c
|
||||
else:
|
||||
in_quotes = not in_quotes
|
||||
backslashes = 0
|
||||
elif c in (' ', '\t') and not in_quotes:
|
||||
# MSDN: Arguments are delimited by white space, which is either
|
||||
# a space or a tab [but only outside of a string].
|
||||
# Flush any buffered backslashes and yield arg, unless empty.
|
||||
arg += '\\' * backslashes
|
||||
if arg:
|
||||
yield arg
|
||||
arg = ''
|
||||
backslashes = 0
|
||||
else:
|
||||
# Flush buffered backslashes and append.
|
||||
arg += '\\' * backslashes
|
||||
arg += c
|
||||
backslashes = 0
|
||||
|
||||
if arg:
|
||||
arg += '\\' * backslashes
|
||||
yield arg
|
||||
|
||||
return list(split_iter(cmdline))
|
||||
|
||||
|
||||
def split_command(cmdstr):
|
||||
""" Split a command string into a list, respecting shell quoting. """
|
||||
if sys.platform.startswith('win'):
|
||||
# shlex.split does not work for Windows command-lines, so special-case
|
||||
# to our own implementation.
|
||||
cmd = win_split(cmdstr)
|
||||
else:
|
||||
cmd = shlex.split(cmdstr)
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def find_include_what_you_use():
|
||||
""" Find IWYU executable and return its full pathname. """
|
||||
env_iwyu_path = os.environ.get('IWYU_BINARY')
|
||||
if env_iwyu_path:
|
||||
return os.path.realpath(env_iwyu_path)
|
||||
|
||||
# Search in same dir as this script.
|
||||
iwyu_path = shutil.which('include-what-you-use',
|
||||
path=os.path.dirname(__file__))
|
||||
if iwyu_path:
|
||||
return os.path.realpath(iwyu_path)
|
||||
|
||||
# Search the system PATH.
|
||||
iwyu_path = shutil.which('include-what-you-use')
|
||||
if iwyu_path:
|
||||
return os.path.realpath(iwyu_path)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
IWYU_EXECUTABLE = find_include_what_you_use()
|
||||
|
||||
|
||||
class Process(object):
|
||||
""" Manages an IWYU process in flight """
|
||||
def __init__(self, proc, outfile):
|
||||
self.proc = proc
|
||||
self.outfile = outfile
|
||||
self.output = None
|
||||
|
||||
def poll(self):
|
||||
""" Return the exit code if the process has completed, None otherwise.
|
||||
"""
|
||||
return self.proc.poll()
|
||||
|
||||
@property
|
||||
def returncode(self):
|
||||
return self.proc.returncode
|
||||
|
||||
def get_output(self):
|
||||
""" Return stdout+stderr output of the process.
|
||||
|
||||
This call blocks until the process is complete, then returns the output.
|
||||
"""
|
||||
if not self.output:
|
||||
self.proc.wait()
|
||||
self.outfile.seek(0)
|
||||
self.output = self.outfile.read().decode("utf-8")
|
||||
self.outfile.close()
|
||||
|
||||
return self.output
|
||||
|
||||
@classmethod
|
||||
def start(cls, invocation):
|
||||
""" Start a Process for the invocation and capture stdout+stderr. """
|
||||
outfile = tempfile.TemporaryFile(prefix='iwyu')
|
||||
process = subprocess.Popen(
|
||||
invocation.command,
|
||||
cwd=invocation.cwd,
|
||||
stdout=outfile,
|
||||
stderr=subprocess.STDOUT)
|
||||
return cls(process, outfile)
|
||||
|
||||
|
||||
KNOWN_COMPILER_WRAPPERS=frozenset([
|
||||
"ccache"
|
||||
])
|
||||
|
||||
|
||||
class Invocation(object):
|
||||
""" Holds arguments of an IWYU invocation. """
|
||||
def __init__(self, command, cwd):
|
||||
self.command = command
|
||||
self.cwd = cwd
|
||||
|
||||
def __str__(self):
|
||||
return ' '.join(self.command)
|
||||
|
||||
@classmethod
|
||||
def from_compile_command(cls, entry, extra_args):
|
||||
""" Parse a JSON compilation database entry into new Invocation. """
|
||||
if 'arguments' in entry:
|
||||
# arguments is a command-line in list form.
|
||||
command = entry['arguments']
|
||||
elif 'command' in entry:
|
||||
# command is a command-line in string form, split to list.
|
||||
command = split_command(entry['command'])
|
||||
else:
|
||||
raise ValueError('Invalid compilation database entry: %s' % entry)
|
||||
|
||||
if command[0] in KNOWN_COMPILER_WRAPPERS:
|
||||
# Remove the compiler wrapper from the command.
|
||||
command = command[1:]
|
||||
|
||||
# Rewrite the compile command for IWYU
|
||||
compile_command, compile_args = command[0], command[1:]
|
||||
if is_msvc_driver(compile_command):
|
||||
# If the compiler is cl-compatible, let IWYU be cl-compatible.
|
||||
extra_args = ['--driver-mode=cl'] + extra_args
|
||||
|
||||
command = [IWYU_EXECUTABLE] + extra_args + compile_args
|
||||
return cls(command, entry['directory'])
|
||||
|
||||
def start(self, verbose):
|
||||
""" Run invocation and collect output. """
|
||||
if verbose:
|
||||
print('# %s' % self, file=sys.stderr)
|
||||
|
||||
return Process.start(self)
|
||||
|
||||
|
||||
def fixup_compilation_db(compilation_db):
|
||||
""" Canonicalize paths in JSON compilation database. """
|
||||
for entry in compilation_db:
|
||||
# Convert relative paths to absolute ones if possible, based on the entry's directory.
|
||||
if 'directory' in entry and not os.path.isabs(entry['file']):
|
||||
entry['file'] = os.path.join(entry['directory'], entry['file'])
|
||||
|
||||
# Expand relative paths and symlinks
|
||||
entry['file'] = os.path.realpath(entry['file'])
|
||||
|
||||
return compilation_db
|
||||
|
||||
|
||||
def select_compilation_db(compilation_db, selection):
|
||||
""" Return a new compilation database reduced to the paths in selection. """
|
||||
if not selection:
|
||||
return compilation_db
|
||||
|
||||
# Canonicalize selection paths to match compilation database.
|
||||
selection = [os.path.realpath(p) for p in selection]
|
||||
|
||||
new_db = []
|
||||
for path in selection:
|
||||
if not os.path.exists(path):
|
||||
print('warning: \'%s\' not found on disk.' % path, file=sys.stderr)
|
||||
continue
|
||||
|
||||
found = [e for e in compilation_db if is_subpath_of(e['file'], path)]
|
||||
if not found:
|
||||
print('warning: \'%s\' not found in compilation database.' % path,
|
||||
file=sys.stderr)
|
||||
continue
|
||||
|
||||
new_db.extend(found)
|
||||
|
||||
return new_db
|
||||
|
||||
def slice_compilation_db(compilation_db, selection, exclude):
|
||||
""" Return a new compilation database with filtered entries. """
|
||||
|
||||
new_db = select_compilation_db(compilation_db, selection)
|
||||
|
||||
# Canonicalize selection paths to match compilation database.
|
||||
exclude = [os.path.realpath(p) for p in exclude]
|
||||
|
||||
for path in exclude:
|
||||
if not os.path.exists(path):
|
||||
print('warning: excluded path \'%s\' not found on disk.' % path,
|
||||
file=sys.stderr)
|
||||
continue
|
||||
|
||||
new_db = [e for e in new_db if not is_subpath_of(e['file'], path)]
|
||||
|
||||
return new_db
|
||||
|
||||
|
||||
def worst_exit_code(worst, cur):
|
||||
"""Return the most extreme exit code of two.
|
||||
|
||||
Negative exit codes occur if the program exits due to a signal (Unix) or
|
||||
structured exception (Windows). If we've seen a negative one before, keep
|
||||
it, as it usually indicates a critical error.
|
||||
|
||||
Otherwise return the biggest positive exit code.
|
||||
"""
|
||||
if cur < 0:
|
||||
# Negative results take precedence, return the minimum
|
||||
return min(worst, cur)
|
||||
elif worst < 0:
|
||||
# We know cur is non-negative, negative worst must be minimum
|
||||
return worst
|
||||
else:
|
||||
# We know neither are negative, return the maximum
|
||||
return max(worst, cur)
|
||||
|
||||
|
||||
def execute(invocations, verbose, formatter, jobs, max_load_average=0):
|
||||
""" Launch processes described by invocations. """
|
||||
exit_code = 0
|
||||
if jobs == 1:
|
||||
for invocation in invocations:
|
||||
proc = invocation.start(verbose)
|
||||
print(formatter(proc.get_output()))
|
||||
exit_code = worst_exit_code(exit_code, proc.returncode)
|
||||
return exit_code
|
||||
|
||||
pending = []
|
||||
while invocations or pending:
|
||||
# Collect completed IWYU processes and print results.
|
||||
complete = [proc for proc in pending if proc.poll() is not None]
|
||||
for proc in complete:
|
||||
pending.remove(proc)
|
||||
print(formatter(proc.get_output()))
|
||||
exit_code = worst_exit_code(exit_code, proc.returncode)
|
||||
|
||||
# Schedule new processes if there's room.
|
||||
capacity = jobs - len(pending)
|
||||
|
||||
if max_load_average > 0:
|
||||
one_min_load_average, _, _ = os.getloadavg()
|
||||
load_capacity = max_load_average - one_min_load_average
|
||||
if load_capacity < 0:
|
||||
load_capacity = 0
|
||||
if load_capacity < capacity:
|
||||
capacity = int(load_capacity)
|
||||
if not capacity and not pending:
|
||||
# Ensure there is at least one job running.
|
||||
capacity = 1
|
||||
|
||||
pending.extend(i.start(verbose) for i in invocations[:capacity])
|
||||
invocations = invocations[capacity:]
|
||||
|
||||
# Yield CPU.
|
||||
time.sleep(0.0001)
|
||||
return exit_code
|
||||
|
||||
|
||||
def main(compilation_db_path, source_files, exclude, verbose, formatter, jobs,
|
||||
max_load_average, extra_args):
|
||||
""" Entry point. """
|
||||
|
||||
if not IWYU_EXECUTABLE:
|
||||
print('error: include-what-you-use executable not found',
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
|
||||
try:
|
||||
if os.path.isdir(compilation_db_path):
|
||||
compilation_db_path = os.path.join(compilation_db_path,
|
||||
'compile_commands.json')
|
||||
|
||||
# Read compilation db from disk.
|
||||
compilation_db_path = os.path.realpath(compilation_db_path)
|
||||
with open(compilation_db_path, 'r') as fileobj:
|
||||
compilation_db = json.load(fileobj)
|
||||
except IOError as why:
|
||||
print('error: failed to parse compilation database: %s' % why,
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
|
||||
compilation_db = fixup_compilation_db(compilation_db)
|
||||
compilation_db = slice_compilation_db(compilation_db, source_files, exclude)
|
||||
|
||||
# Transform compilation db entries into a list of IWYU invocations.
|
||||
invocations = [
|
||||
Invocation.from_compile_command(e, extra_args) for e in compilation_db
|
||||
]
|
||||
|
||||
return execute(invocations, verbose, formatter, jobs, max_load_average)
|
||||
|
||||
|
||||
def _bootstrap(sys_argv):
|
||||
""" Parse arguments and dispatch to main(). """
|
||||
|
||||
# This hackery is necessary to add the forwarded IWYU args to the
|
||||
# usage and help strings.
|
||||
def customize_usage(parser):
|
||||
""" Rewrite the parser's format_usage. """
|
||||
original_format_usage = parser.format_usage
|
||||
parser.format_usage = lambda: original_format_usage().rstrip() + \
|
||||
' -- [<IWYU args>]' + os.linesep
|
||||
|
||||
def customize_help(parser):
|
||||
""" Rewrite the parser's format_help. """
|
||||
original_format_help = parser.format_help
|
||||
|
||||
def custom_help():
|
||||
""" Customized help string, calls the adjusted format_usage. """
|
||||
helpmsg = original_format_help()
|
||||
helplines = helpmsg.splitlines()
|
||||
helplines[0] = parser.format_usage().rstrip()
|
||||
return os.linesep.join(helplines) + os.linesep
|
||||
|
||||
parser.format_help = custom_help
|
||||
|
||||
# Parse arguments.
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Include-what-you-use compilation database driver.',
|
||||
epilog='Assumes include-what-you-use is available on the PATH.')
|
||||
customize_usage(parser)
|
||||
customize_help(parser)
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
help='Print IWYU commands')
|
||||
parser.add_argument('-o', '--output-format', type=str,
|
||||
choices=FORMATTERS.keys(), default=DEFAULT_FORMAT,
|
||||
help='Output format (default: %s)' % DEFAULT_FORMAT)
|
||||
parser.add_argument('-j', '--jobs', type=int, default=1,
|
||||
nargs='?', const=0,
|
||||
help=('Number of concurrent subprocesses. If zero, '
|
||||
'will try to match the logical cores of the '
|
||||
'system.'))
|
||||
parser.add_argument('-l', '--load', type=float, default=0,
|
||||
help=('Do not start new jobs if the 1min load average '
|
||||
'is greater than the provided value'))
|
||||
parser.add_argument('-p', metavar='<build-path>', required=True,
|
||||
help='Compilation database path', dest='dbpath')
|
||||
parser.add_argument('-e', '--exclude', action='append', default=[],
|
||||
help=('Do not run IWYU on source files (or directories) '
|
||||
'below this path.'))
|
||||
parser.add_argument('source', nargs='*',
|
||||
help=('Zero or more source files (or directories) to '
|
||||
'run IWYU on. Defaults to all in compilation '
|
||||
'database.'))
|
||||
|
||||
def partition_args(argv):
|
||||
""" Split around '--' into driver args and IWYU args. """
|
||||
try:
|
||||
double_dash = argv.index('--')
|
||||
return argv[:double_dash], argv[double_dash+1:]
|
||||
except ValueError:
|
||||
return argv, []
|
||||
argv, extra_args = partition_args(sys_argv[1:])
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
jobs = args.jobs
|
||||
if jobs == 0:
|
||||
jobs = os.cpu_count() or 1
|
||||
|
||||
return main(args.dbpath, args.source, args.exclude, args.verbose,
|
||||
FORMATTERS[args.output_format], jobs, args.load, extra_args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(_bootstrap(sys.argv))
|
||||
147
tools/linter/run_clang-tidy.sh
Normal file
147
tools/linter/run_clang-tidy.sh
Normal file
@@ -0,0 +1,147 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script para ejecutar clang-tidy recursivamente en todo el código fuente
|
||||
# Uso:
|
||||
# ./run_clang-tidy.sh [--fix] [--help] [archivos...]
|
||||
# --fix: Aplica las correcciones automáticamente (opcional)
|
||||
# --help: Muestra la ayuda
|
||||
# archivos: Archivos específicos a analizar (opcional, si no se especifica analiza todos)
|
||||
|
||||
# Función de ayuda
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Uso: ./run_clang-tidy.sh [OPCIONES] [ARCHIVOS...]
|
||||
|
||||
Script para ejecutar clang-tidy en el código fuente del proyecto.
|
||||
|
||||
OPCIONES:
|
||||
--fix Aplica las correcciones automáticamente
|
||||
--help Muestra este mensaje de ayuda
|
||||
|
||||
ARCHIVOS:
|
||||
Si no se especifican archivos, se analizan todos los archivos del proyecto
|
||||
(excluyendo la carpeta external/ y jail_audio.hpp). Si se especifican archivos,
|
||||
solo se analizan esos archivos específicos.
|
||||
|
||||
EJEMPLOS:
|
||||
# Analizar todo el proyecto
|
||||
./run_clang-tidy.sh
|
||||
|
||||
# Analizar todo el proyecto y aplicar correcciones
|
||||
./run_clang-tidy.sh --fix
|
||||
|
||||
# Analizar un archivo específico
|
||||
./run_clang-tidy.sh source/game/entities/enemy.cpp
|
||||
|
||||
# Analizar múltiples archivos
|
||||
./run_clang-tidy.sh source/game/entities/enemy.cpp source/game/entities/player.cpp
|
||||
|
||||
# Analizar y corregir un archivo específico
|
||||
./run_clang-tidy.sh --fix source/game/entities/enemy.cpp
|
||||
|
||||
# Analizar todos los archivos de una carpeta (usando glob)
|
||||
./run_clang-tidy.sh source/game/entities/*.cpp
|
||||
|
||||
NOTAS:
|
||||
- Se requiere que el directorio build/ exista y contenga compile_commands.json
|
||||
- Los archivos se procesan en paralelo (4 procesos)
|
||||
- Solo se procesan archivos .cpp, .hpp y .h
|
||||
- Las rutas pueden ser relativas o absolutas
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Auto-detectar la ubicación del script y calcular rutas del proyecto
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
PROJECT_DIR=$(cd "$SCRIPT_DIR/../.." && pwd)
|
||||
SOURCE_DIR="$PROJECT_DIR/source"
|
||||
BUILD_DIR="$PROJECT_DIR/build"
|
||||
|
||||
# Parsear argumentos: separar --fix, --help de archivos
|
||||
FIX_FLAG=""
|
||||
FILES=()
|
||||
|
||||
for arg in "$@"; do
|
||||
if [[ "$arg" == "--fix" ]]; then
|
||||
FIX_FLAG="--fix"
|
||||
elif [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
|
||||
show_help
|
||||
else
|
||||
FILES+=("$arg")
|
||||
fi
|
||||
done
|
||||
|
||||
# Mostrar modo
|
||||
if [[ -n "$FIX_FLAG" ]]; then
|
||||
echo "Modo: Aplicando correcciones automáticamente (--fix)"
|
||||
else
|
||||
echo "Modo: Solo análisis (sin --fix)"
|
||||
fi
|
||||
echo
|
||||
|
||||
# Verificar que los directorios existen
|
||||
if [[ ! -d "$SOURCE_DIR" ]]; then
|
||||
echo "Error: El directorio source/ no existe en: $SOURCE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "$BUILD_DIR" ]]; then
|
||||
echo "Error: El directorio build/ no existe en: $BUILD_DIR"
|
||||
echo "Ejecuta primero: cmake -B build && cmake --build build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Proyecto: $PROJECT_DIR"
|
||||
echo "Fuentes: $SOURCE_DIR"
|
||||
echo "Build: $BUILD_DIR"
|
||||
echo
|
||||
|
||||
# Si se especificaron archivos, procesarlos directamente
|
||||
if [[ ${#FILES[@]} -gt 0 ]]; then
|
||||
echo "=== Procesando ${#FILES[@]} archivo(s) específico(s) ==="
|
||||
|
||||
# Validar y procesar cada archivo
|
||||
VALID_FILES=()
|
||||
for file in "${FILES[@]}"; do
|
||||
# Convertir a ruta absoluta si es relativa
|
||||
if [[ ! "$file" = /* ]]; then
|
||||
file="$PROJECT_DIR/$file"
|
||||
fi
|
||||
|
||||
# Verificar que existe
|
||||
if [[ ! -f "$file" ]]; then
|
||||
echo "Advertencia: Archivo no encontrado: $file (omitiendo)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Verificar extensión
|
||||
if [[ "$file" =~ \.(cpp|hpp|h)$ ]]; then
|
||||
VALID_FILES+=("$file")
|
||||
else
|
||||
echo "Advertencia: Archivo no es C++: $file (omitiendo)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Si no hay archivos válidos, salir
|
||||
if [[ ${#VALID_FILES[@]} -eq 0 ]]; then
|
||||
echo "Error: No hay archivos válidos para procesar"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
# Procesar archivos en paralelo
|
||||
printf '%s\n' "${VALID_FILES[@]}" | \
|
||||
xargs -P4 -I{} bash -c 'echo "Procesando: {}"; clang-tidy {} -p '"$BUILD_DIR"' '"$FIX_FLAG"
|
||||
|
||||
echo
|
||||
echo "¡Proceso completado para ${#VALID_FILES[@]} archivo(s)!"
|
||||
else
|
||||
# Comportamiento original: procesar todos los archivos
|
||||
echo "=== Escaneando recursivamente source/ (excluyendo external/ y jail_audio.hpp) ==="
|
||||
find "$SOURCE_DIR" \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) -not -path "*/external/*" -not -path "*/core/audio/jail_audio.hpp" -print0 | \
|
||||
xargs -0 -P4 -I{} bash -c 'echo "Procesando: {}"; clang-tidy {} -p '"$BUILD_DIR"' '"$FIX_FLAG"
|
||||
|
||||
echo
|
||||
echo "¡Proceso completado para todos los archivos!"
|
||||
fi
|
||||
196
tools/linter/run_cppcheck.sh
Normal file
196
tools/linter/run_cppcheck.sh
Normal file
@@ -0,0 +1,196 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# CONFIGURACIÓN - Editar estas constantes para personalizar el script
|
||||
# ============================================================================
|
||||
|
||||
# Detectar la ubicación del script y la raíz del proyecto
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Rutas del proyecto
|
||||
DEFAULT_ANALYSIS_PATH="$PROJECT_ROOT/source"
|
||||
INCLUDE_PATH="$PROJECT_ROOT/source"
|
||||
SUPPRESSIONS_FILE="$SCRIPT_DIR/cppcheck_suppressions"
|
||||
OUTPUT_DIR="$SCRIPT_DIR"
|
||||
BUILD_DIR="$PROJECT_ROOT/build"
|
||||
COMPILE_COMMANDS="$BUILD_DIR/compile_commands.json"
|
||||
|
||||
# Parámetros de compilación comunes (fallback para análisis de archivos específicos)
|
||||
CPPCHECK_STD="--std=c++20"
|
||||
|
||||
# Calcular número de threads (cores - 1, mínimo 1)
|
||||
THREADS=$(($(nproc) - 1))
|
||||
[[ $THREADS -lt 1 ]] && THREADS=1
|
||||
|
||||
# ============================================================================
|
||||
|
||||
# Función para asegurar que compile_commands.json existe
|
||||
ensure_compile_commands() {
|
||||
if [[ ! -f "$COMPILE_COMMANDS" ]]; then
|
||||
echo "⚠ compile_commands.json no encontrado en $BUILD_DIR"
|
||||
echo "Generando compile_commands.json con CMake..."
|
||||
|
||||
# Crear directorio build si no existe
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# Ejecutar CMake para generar compile_commands.json
|
||||
(cd "$BUILD_DIR" && cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. > /dev/null 2>&1)
|
||||
|
||||
if [[ ! -f "$COMPILE_COMMANDS" ]]; then
|
||||
echo "❌ Error: No se pudo generar compile_commands.json"
|
||||
echo "Por favor, ejecuta manualmente:"
|
||||
echo " cd $BUILD_DIR && cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ compile_commands.json generado exitosamente"
|
||||
else
|
||||
echo "✓ Usando compile_commands.json existente: $COMPILE_COMMANDS"
|
||||
fi
|
||||
}
|
||||
|
||||
# Función para mostrar el uso del script
|
||||
mostrar_uso() {
|
||||
echo "Uso: $0 [OPCIÓN] [RUTA]"
|
||||
echo ""
|
||||
echo "Opciones de análisis:"
|
||||
echo " -w, --warning Ejecutar cppcheck con warning, style, performance"
|
||||
echo " -a, --all Ejecutar cppcheck con todas las opciones habilitadas"
|
||||
echo " -u, --unused Ejecutar cppcheck para unusedFunction"
|
||||
echo ""
|
||||
echo "Opciones de ruta:"
|
||||
echo " -p, --path <ruta> Especificar ruta a analizar (default: PROJECT_ROOT/source)"
|
||||
echo " [RUTA] Ruta como último argumento posicional"
|
||||
echo ""
|
||||
echo "Ejemplos:"
|
||||
echo " $0 --warning"
|
||||
echo " $0 -w --path ../src/"
|
||||
echo " $0 --all /path/to/code/"
|
||||
echo " $0 -u ../source/"
|
||||
}
|
||||
|
||||
# Inicializar las variables
|
||||
opcion=""
|
||||
ruta="$DEFAULT_ANALYSIS_PATH"
|
||||
|
||||
# Procesar las opciones
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-w|--warning)
|
||||
opcion="warning"
|
||||
shift
|
||||
;;
|
||||
-a|--all)
|
||||
opcion="all"
|
||||
shift
|
||||
;;
|
||||
-u|--unused)
|
||||
opcion="unused"
|
||||
shift
|
||||
;;
|
||||
-p|--path)
|
||||
if [[ -n "$2" && "$2" != -* ]]; then
|
||||
ruta="$2"
|
||||
shift 2
|
||||
else
|
||||
echo "Error: --path requiere un argumento"
|
||||
mostrar_uso
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
-h|--help)
|
||||
mostrar_uso
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
echo "Error: Opción desconocida: $1"
|
||||
mostrar_uso
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
# Argumento posicional (asumimos que es la ruta)
|
||||
ruta="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Verificar que se haya especificado una opción de análisis
|
||||
if [[ -z "$opcion" ]]; then
|
||||
echo "Error: Debe especificar una opción de análisis (-w, -a, o -u)"
|
||||
mostrar_uso
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ejecutar según la opción seleccionada
|
||||
case $opcion in
|
||||
warning)
|
||||
ensure_compile_commands
|
||||
if [[ "$ruta" == "$DEFAULT_ANALYSIS_PATH" ]]; then
|
||||
echo "Ejecutando cppcheck con warning, style, performance en proyecto completo (usando $THREADS threads)"
|
||||
cppcheck --force --enable=warning,style,performance \
|
||||
--check-level=exhaustive \
|
||||
-j$THREADS \
|
||||
--project="$COMPILE_COMMANDS" \
|
||||
--suppressions-list="$SUPPRESSIONS_FILE" \
|
||||
2>"$OUTPUT_DIR/cppcheck-result-warning-style-performance.txt"
|
||||
else
|
||||
echo "Ejecutando cppcheck con warning, style, performance en: $ruta (usando $THREADS threads)"
|
||||
cppcheck --force --enable=warning,style,performance $CPPCHECK_STD \
|
||||
--check-level=exhaustive \
|
||||
-j$THREADS \
|
||||
-I "$INCLUDE_PATH" \
|
||||
--suppressions-list="$SUPPRESSIONS_FILE" \
|
||||
"$ruta" \
|
||||
2>"$OUTPUT_DIR/cppcheck-result-warning-style-performance.txt"
|
||||
fi
|
||||
echo "Resultados guardados en: $OUTPUT_DIR/cppcheck-result-warning-style-performance.txt"
|
||||
;;
|
||||
all)
|
||||
ensure_compile_commands
|
||||
if [[ "$ruta" == "$DEFAULT_ANALYSIS_PATH" ]]; then
|
||||
echo "Ejecutando cppcheck con todas las opciones en proyecto completo (usando $THREADS threads)"
|
||||
cppcheck --force --enable=all \
|
||||
-j$THREADS \
|
||||
--project="$COMPILE_COMMANDS" \
|
||||
--suppress=missingIncludeSystem \
|
||||
--suppressions-list="$SUPPRESSIONS_FILE" \
|
||||
2>"$OUTPUT_DIR/cppcheck-result-all.txt"
|
||||
else
|
||||
echo "Ejecutando cppcheck con todas las opciones en: $ruta (usando $THREADS threads)"
|
||||
cppcheck --force --enable=all -I /usr/include -I "$INCLUDE_PATH" $CPPCHECK_STD \
|
||||
-j$THREADS \
|
||||
--suppress=missingIncludeSystem \
|
||||
--suppressions-list="$SUPPRESSIONS_FILE" \
|
||||
"$ruta" \
|
||||
2>"$OUTPUT_DIR/cppcheck-result-all.txt"
|
||||
fi
|
||||
echo "Resultados guardados en: $OUTPUT_DIR/cppcheck-result-all.txt"
|
||||
;;
|
||||
unused)
|
||||
ensure_compile_commands
|
||||
if [[ "$ruta" == "$DEFAULT_ANALYSIS_PATH" ]]; then
|
||||
echo "Ejecutando cppcheck para unusedFunction en proyecto completo (usando $THREADS threads)"
|
||||
cppcheck --enable=style \
|
||||
-j$THREADS \
|
||||
--project="$COMPILE_COMMANDS" \
|
||||
--suppressions-list="$SUPPRESSIONS_FILE" \
|
||||
2>"$OUTPUT_DIR/cppcheck-result-unusedFunction.txt"
|
||||
else
|
||||
echo "Ejecutando cppcheck para unusedFunction en: $ruta (usando $THREADS threads)"
|
||||
cppcheck --enable=style $CPPCHECK_STD \
|
||||
-j$THREADS \
|
||||
-I "$INCLUDE_PATH" \
|
||||
--suppressions-list="$SUPPRESSIONS_FILE" \
|
||||
"$ruta" \
|
||||
2>"$OUTPUT_DIR/cppcheck-result-unusedFunction.txt"
|
||||
fi
|
||||
echo "Resultados guardados en: $OUTPUT_DIR/cppcheck-result-unusedFunction.txt"
|
||||
;;
|
||||
*)
|
||||
echo "Error: Opción inválida"
|
||||
mostrar_uso
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
27
tools/linter/run_iwyu.sh
Normal file
27
tools/linter/run_iwyu.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🏁 Auto-detectar ruta base del proyecto
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# 📁 Ruta al build
|
||||
BUILD_DIR="$PROJECT_ROOT/build"
|
||||
|
||||
# 📄 Archivo de mapping personalizado
|
||||
MAPPING_FILE="$SCRIPT_DIR/sdl3_mapping.imp"
|
||||
|
||||
# 📦 Generar compile_commands.json
|
||||
echo "🔧 Generando compile_commands.json..."
|
||||
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -S "$PROJECT_ROOT" -B "$BUILD_DIR"
|
||||
|
||||
# 🛠️ Ejecutar IWYU con fix_includes.py
|
||||
echo "🚀 Ejecutando IWYU..."
|
||||
./iwyu_tool.py -p "$BUILD_DIR" -- -Xiwyu --mapping_file="$MAPPING_FILE" -Xiwyu --verbose=3 \
|
||||
| fix_include --update_comments --reorder --nosafe_headers
|
||||
|
||||
# 🧹 Reemplazar // for por // Para en líneas de #include
|
||||
echo "✍️ Corrigiendo comentarios en includes..."
|
||||
find "$PROJECT_ROOT" -type f \( -name "*.cpp" -o -name "*.h" \) -exec \
|
||||
sed -i '/^#include .*\/\/ for/s/\/\/ for/\/\/ Para/' {} +
|
||||
|
||||
echo "✅ Script completado."
|
||||
27
tools/linter/run_iwyu_dry_run.sh
Normal file
27
tools/linter/run_iwyu_dry_run.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🏁 Auto-detectar ruta base del proyecto
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# 📁 Ruta al build
|
||||
BUILD_DIR="$PROJECT_ROOT/build"
|
||||
|
||||
# 📄 Archivo de mapping personalizado
|
||||
MAPPING_FILE="$SCRIPT_DIR/sdl3_mapping.imp"
|
||||
|
||||
# 📦 Generar compile_commands.json
|
||||
echo "🔧 Generando compile_commands.json..."
|
||||
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -S "$PROJECT_ROOT" -B "$BUILD_DIR"
|
||||
|
||||
# 🛠️ Ejecutar IWYU con fix_includes.py
|
||||
echo "🚀 Ejecutando IWYU..."
|
||||
iwyu_tool.py -p "$BUILD_DIR" -- -Xiwyu --mapping_file="$MAPPING_FILE" -Xiwyu --verbose=3 \
|
||||
| python3 /usr/bin/fix_includes.py --update_comments --reorder --nosafe_headers --dry_run
|
||||
|
||||
# 🧹 Reemplazar // for por // Para en líneas de #include
|
||||
echo "✍️ Corrigiendo comentarios en includes..."
|
||||
find "$PROJECT_ROOT" -type f \( -name "*.cpp" -o -name "*.h" \) -exec \
|
||||
sed -i '/^#include .*\/\/ for/s/\/\/ for/\/\/ Para/' {} +
|
||||
|
||||
echo "✅ Script completado."
|
||||
27
tools/linter/run_valgrind.sh
Normal file
27
tools/linter/run_valgrind.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🏁 Auto-detectar ruta base del proyecto
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# 📦 Ejecutable (por defecto el del proyecto, o el pasado como argumento)
|
||||
EXECUTABLE="${1:-$PROJECT_ROOT/build/pollo}"
|
||||
|
||||
# 📄 Archivo de salida
|
||||
OUTPUT_FILE="$SCRIPT_DIR/valgrind_out.txt"
|
||||
|
||||
if [[ ! -x "$EXECUTABLE" ]]; then
|
||||
echo "❌ Error: Ejecutable no encontrado o no es ejecutable: $EXECUTABLE"
|
||||
echo "Uso: $0 [ruta_al_ejecutable]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔍 Ejecutando valgrind en: $EXECUTABLE"
|
||||
echo "📄 Salida en: $OUTPUT_FILE"
|
||||
|
||||
valgrind --suppressions="$SCRIPT_DIR/valgrind_exceptions" \
|
||||
--leak-check=full \
|
||||
"$EXECUTABLE" \
|
||||
> "$OUTPUT_FILE" 2>&1
|
||||
|
||||
echo "✅ Valgrind completado. Revisa $OUTPUT_FILE"
|
||||
57
tools/linter/sdl3_mapping.imp
Normal file
57
tools/linter/sdl3_mapping.imp
Normal file
@@ -0,0 +1,57 @@
|
||||
[
|
||||
{ "include": ["<SDL3/SDL_stdinc.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_assert.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_asyncio.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_atomic.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_audio.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_bits.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_blendmode.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_camera.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_clipboard.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_cpuinfo.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_dialog.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_endian.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_error.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_events.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_filesystem.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_gamepad.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_gpu.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_guid.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_haptic.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_hidapi.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_hints.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_init.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_iostream.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_joystick.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_keyboard.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_keycode.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_loadso.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_locale.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_log.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_messagebox.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_metal.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_misc.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_mouse.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_mutex.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_pen.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_pixels.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_platform.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_power.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_process.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_properties.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_rect.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_render.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_scancode.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_sensor.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_storage.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_surface.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_system.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_thread.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_time.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_timer.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_tray.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_touch.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_version.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_video.h>", "private", "<SDL3/SDL.h>", "public"] },
|
||||
{ "include": ["<SDL3/SDL_oldnames.h>", "private", "<SDL3/SDL.h>", "public"] }
|
||||
]
|
||||
12
tools/linter/valgrind_exceptions
Normal file
12
tools/linter/valgrind_exceptions
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
ignore_unversioned_libs
|
||||
Memcheck:Leak
|
||||
...
|
||||
obj:*/lib*/lib*.so
|
||||
}
|
||||
{
|
||||
ignore_versioned_libs
|
||||
Memcheck:Leak
|
||||
...
|
||||
obj:*/lib*/lib*.so.*
|
||||
}
|
||||
53
tools/pack_resources/Makefile
Normal file
53
tools/pack_resources/Makefile
Normal file
@@ -0,0 +1,53 @@
|
||||
# Makefile for pack_resources tool
|
||||
|
||||
# Compiler
|
||||
CXX := g++
|
||||
CXXFLAGS := -std=c++20 -Wall -Wextra -O2
|
||||
|
||||
# Directories
|
||||
TOOL_DIR := .
|
||||
SOURCE_DIR := ../../source/core/resources
|
||||
|
||||
# Source files
|
||||
SOURCES := pack_resources.cpp \
|
||||
$(SOURCE_DIR)/resource_pack.cpp
|
||||
|
||||
# Output
|
||||
TARGET := pack_resources
|
||||
|
||||
# Platform-specific executable extension
|
||||
ifeq ($(OS),Windows_NT)
|
||||
TARGET := $(TARGET).exe
|
||||
endif
|
||||
|
||||
# Default target
|
||||
all: $(TARGET)
|
||||
|
||||
# Build the tool
|
||||
$(TARGET): $(SOURCES)
|
||||
@echo "Building pack_resources tool..."
|
||||
$(CXX) $(CXXFLAGS) $(SOURCES) -o $(TARGET)
|
||||
@echo "Build complete: $(TARGET)"
|
||||
|
||||
# Test: create a test pack
|
||||
test: $(TARGET)
|
||||
@echo "Creating test pack..."
|
||||
./$(TARGET) ../../data test_resources.pack
|
||||
|
||||
# Create the actual resources.pack
|
||||
pack: $(TARGET)
|
||||
@echo "Creating resources.pack..."
|
||||
./$(TARGET) ../../data ../../resources.pack
|
||||
|
||||
# List contents of a pack
|
||||
list: $(TARGET)
|
||||
@echo "Listing pack contents..."
|
||||
./$(TARGET) --list ../../resources.pack
|
||||
|
||||
# Clean
|
||||
clean:
|
||||
@echo "Cleaning..."
|
||||
rm -f $(TARGET) test_resources.pack
|
||||
@echo "Clean complete"
|
||||
|
||||
.PHONY: all test pack list clean
|
||||
138
tools/pack_resources/pack_resources.cpp
Normal file
138
tools/pack_resources/pack_resources.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
// pack_resources.cpp
|
||||
// Tool to pack game resources into a single encrypted file
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Include the ResourcePack class
|
||||
#include "../../source/core/resources/resource_pack.hpp"
|
||||
|
||||
void printUsage(const char* program_name) {
|
||||
std::cout << "Usage:\n";
|
||||
std::cout << " " << program_name << " [input_dir] [output_file]\n";
|
||||
std::cout << " " << program_name << " --list <pack_file>\n";
|
||||
std::cout << "\n";
|
||||
std::cout << "Examples:\n";
|
||||
std::cout << " " << program_name << " data resources.pack\n";
|
||||
std::cout << " " << program_name << " --list resources.pack\n";
|
||||
std::cout << "\n";
|
||||
std::cout << "Options:\n";
|
||||
std::cout << " --list List contents of a pack file\n";
|
||||
}
|
||||
|
||||
auto handleDefaultPack() -> int {
|
||||
std::string input_dir = "data";
|
||||
std::string output_file = "resources.pack";
|
||||
|
||||
std::cout << "Using defaults:\n";
|
||||
std::cout << " Input: " << input_dir << "/\n";
|
||||
std::cout << " Output: " << output_file << "\n\n";
|
||||
|
||||
Resource::Pack pack;
|
||||
if (!pack.addDirectory(input_dir)) {
|
||||
std::cerr << "Error: Failed to add directory: " << input_dir << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Add config/assets.yaml to pack (required for release builds)
|
||||
std::cout << "\nAdding config files...\n";
|
||||
if (!pack.addFile("config/assets.yaml", "config/assets.yaml")) {
|
||||
std::cerr << "Warning: Failed to add config/assets.yaml (optional)\n";
|
||||
}
|
||||
|
||||
if (!pack.savePack(output_file)) {
|
||||
std::cerr << "Error: Failed to save pack file: " << output_file << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "\nSuccess! Pack created: " << output_file << '\n';
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto handleListContents(const std::string& pack_file) -> int {
|
||||
std::cout << "Loading pack: " << pack_file << "\n\n";
|
||||
|
||||
Resource::Pack pack;
|
||||
if (!pack.loadPack(pack_file)) {
|
||||
std::cerr << "Error: Failed to load pack file: " << pack_file << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "\nPack Contents:\n";
|
||||
std::cout << "==============\n";
|
||||
|
||||
auto resources = pack.getResourceList();
|
||||
size_t total_size = 0;
|
||||
|
||||
for (const auto& resource : resources) {
|
||||
auto data = pack.getResource(resource);
|
||||
total_size += data.size();
|
||||
std::cout << " " << resource << " (" << data.size() << " bytes)\n";
|
||||
}
|
||||
|
||||
std::cout << "\nTotal Resources: " << resources.size() << '\n';
|
||||
std::cout << "Total Size: " << total_size << " bytes\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto handlePackDirectory(const std::string& input_dir, const std::string& output_file) -> int {
|
||||
std::cout << "Input: " << input_dir << "/\n";
|
||||
std::cout << "Output: " << output_file << "\n\n";
|
||||
|
||||
Resource::Pack pack;
|
||||
if (!pack.addDirectory(input_dir)) {
|
||||
std::cerr << "Error: Failed to add directory: " << input_dir << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Add config/assets.yaml to pack (required for release builds)
|
||||
std::cout << "\nAdding config files...\n";
|
||||
if (!pack.addFile("config/assets.yaml", "config/assets.yaml")) {
|
||||
std::cerr << "Warning: Failed to add config/assets.yaml (optional)\n";
|
||||
}
|
||||
|
||||
if (!pack.savePack(output_file)) {
|
||||
std::cerr << "Error: Failed to save pack file: " << output_file << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "\nSuccess! Pack created: " << output_file << '\n';
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto main(int argc, char* argv[]) -> int {
|
||||
std::cout << "JailDoctor's Dilemma - Resource Packer\n";
|
||||
std::cout << "======================================\n\n";
|
||||
|
||||
// Default behavior: pack data/ into resources.pack
|
||||
if (argc == 1) {
|
||||
return handleDefaultPack();
|
||||
}
|
||||
|
||||
// Help command
|
||||
if (argc == 2) {
|
||||
std::string arg = argv[1];
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
printUsage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// List contents or pack directory
|
||||
if (argc == 3) {
|
||||
std::string arg1 = argv[1];
|
||||
std::string arg2 = argv[2];
|
||||
|
||||
if (arg1 == "--list" || arg1 == "-l") {
|
||||
return handleListContents(arg2);
|
||||
}
|
||||
|
||||
return handlePackDirectory(arg1, arg2);
|
||||
}
|
||||
|
||||
// Invalid arguments
|
||||
std::cerr << "Error: Invalid arguments\n\n";
|
||||
printUsage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
51
tools/run_headless.sh
Normal file
51
tools/run_headless.sh
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
# Script para ejecutar JailDoctor's Dilemma en modo headless (sin pantalla)
|
||||
# Usa Xvfb (X Virtual Framebuffer) para simular un display
|
||||
|
||||
# Configuración
|
||||
GAME_EXECUTABLE="../pollo"
|
||||
DISPLAY_SIZE="1280x720x24" # Resolución y profundidad de color
|
||||
|
||||
# Colores para output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}=== JailDoctor's Dilemma - Modo Headless ===${NC}"
|
||||
echo ""
|
||||
|
||||
# Verificar que Xvfb está instalado
|
||||
if ! command -v xvfb-run &> /dev/null; then
|
||||
echo -e "${YELLOW}ERROR: Xvfb no está instalado${NC}"
|
||||
echo "Instálalo con: sudo apt-get install xvfb"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verificar que el ejecutable existe
|
||||
if [ ! -f "$GAME_EXECUTABLE" ]; then
|
||||
echo -e "${YELLOW}ERROR: No se encuentra el ejecutable $GAME_EXECUTABLE${NC}"
|
||||
echo "Compila el juego primero con: cmake --build build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Iniciando juego con Xvfb..."
|
||||
echo "Display virtual: $DISPLAY_SIZE"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Nota: El juego está corriendo. Presiona Ctrl+C para detenerlo.${NC}"
|
||||
echo ""
|
||||
|
||||
# Ejecutar el juego con Xvfb
|
||||
# Opciones:
|
||||
# -a: Selecciona automáticamente un número de display disponible
|
||||
# -s: Configura el servidor X (resolución y profundidad)
|
||||
xvfb-run -a -s "-screen 0 ${DISPLAY_SIZE}" "$GAME_EXECUTABLE" "$@"
|
||||
|
||||
EXIT_CODE=$?
|
||||
echo ""
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo -e "${GREEN}Juego finalizado correctamente${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Juego finalizado con código de salida: $EXIT_CODE${NC}"
|
||||
fi
|
||||
|
||||
exit $EXIT_CODE
|
||||
293
tools/tmx_to_yaml.py
Normal file
293
tools/tmx_to_yaml.py
Normal file
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script para migrar tilemaps de archivos TMX (Tiled) a archivos YAML.
|
||||
|
||||
Uso:
|
||||
python tools/tmx_to_yaml.py
|
||||
|
||||
Busca todos los archivos .tmx en data/room/ y actualiza el tilemap
|
||||
en el archivo .yaml correspondiente.
|
||||
"""
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
def parse_layer_csv(data_element, width: int, height: int, offset: int) -> list[list[int]]:
|
||||
"""
|
||||
Parsea los datos CSV de una capa y convierte los indices.
|
||||
|
||||
Args:
|
||||
data_element: Elemento XML con los datos CSV
|
||||
width: Ancho del mapa en tiles
|
||||
height: Alto del mapa en tiles
|
||||
offset: Valor a restar de los indices (excepto 0 que pasa a -1)
|
||||
|
||||
Returns:
|
||||
Lista de filas, cada fila es una lista de indices
|
||||
"""
|
||||
encoding = data_element.attrib.get('encoding', '')
|
||||
if encoding != 'csv':
|
||||
raise ValueError(f"Encoding no soportado: {encoding}. Solo se soporta CSV.")
|
||||
|
||||
csv_text = data_element.text.strip()
|
||||
all_values = []
|
||||
for val in csv_text.replace('\n', ',').split(','):
|
||||
val = val.strip()
|
||||
if val:
|
||||
all_values.append(int(val))
|
||||
|
||||
# Convertir indices: 0 -> -1, N -> N - offset
|
||||
result = []
|
||||
for y in range(height):
|
||||
row = []
|
||||
for x in range(width):
|
||||
idx = y * width + x
|
||||
tmx_val = all_values[idx]
|
||||
if tmx_val == 0:
|
||||
yaml_val = -1
|
||||
else:
|
||||
yaml_val = tmx_val - offset
|
||||
row.append(yaml_val)
|
||||
result.append(row)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def parse_tmx(tmx_path: Path) -> tuple[list[list[int]], list[list[int]] | None, int, int]:
|
||||
"""
|
||||
Parsea un archivo TMX y extrae el tilemap y collisionmap.
|
||||
|
||||
Returns:
|
||||
tilemap: Lista de filas para el tilemap (layer id=1)
|
||||
collisionmap: Lista de filas para el collisionmap (layer id=2), o None si no existe
|
||||
width: Ancho del mapa en tiles
|
||||
height: Alto del mapa en tiles
|
||||
"""
|
||||
tree = ET.parse(tmx_path)
|
||||
root = tree.getroot()
|
||||
|
||||
width = int(root.attrib['width'])
|
||||
height = int(root.attrib['height'])
|
||||
|
||||
# Obtener firstgid del primer tileset (para tilemap)
|
||||
tileset = root.find('tileset')
|
||||
firstgid = int(tileset.attrib['firstgid']) if tileset is not None else 1
|
||||
|
||||
# Buscar las capas por id
|
||||
layers = root.findall('layer')
|
||||
layer1 = None
|
||||
layer2 = None
|
||||
for layer in layers:
|
||||
layer_id = layer.attrib.get('id')
|
||||
if layer_id == '1':
|
||||
layer1 = layer
|
||||
elif layer_id == '2':
|
||||
layer2 = layer
|
||||
|
||||
# Parsear layer 1 (tilemap) - obligatoria
|
||||
if layer1 is None:
|
||||
raise ValueError(f"No se encontro layer id=1 en {tmx_path}")
|
||||
|
||||
data1 = layer1.find('data')
|
||||
if data1 is None:
|
||||
raise ValueError(f"No se encontraron datos en layer 1 de {tmx_path}")
|
||||
|
||||
tilemap = parse_layer_csv(data1, width, height, firstgid)
|
||||
|
||||
# Parsear layer 2 (collisionmap) - opcional
|
||||
collisionmap = None
|
||||
if layer2 is not None:
|
||||
data2 = layer2.find('data')
|
||||
if data2 is not None:
|
||||
# Para collisionmap: 0 -> -1, N -> N - 576
|
||||
collisionmap = parse_layer_csv(data2, width, height, 576)
|
||||
|
||||
return tilemap, collisionmap, width, height
|
||||
|
||||
|
||||
def format_tilemap_yaml(tilemap: list[list[int]], width: int, height: int) -> str:
|
||||
"""
|
||||
Formatea el tilemap como YAML.
|
||||
"""
|
||||
lines = []
|
||||
lines.append(f"# Tilemap: {height} filas x {width} columnas ({width*8}x{height*8} pixeles @ 8px/tile)")
|
||||
lines.append("# Indices de tiles (-1 = vacio)")
|
||||
lines.append("tilemap:")
|
||||
|
||||
for row in tilemap:
|
||||
row_str = ", ".join(str(v) for v in row)
|
||||
lines.append(f" - [{row_str}]")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def format_collisionmap_yaml(collisionmap: list[list[int]], width: int, height: int) -> str:
|
||||
"""
|
||||
Formatea el collisionmap como YAML.
|
||||
"""
|
||||
lines = []
|
||||
lines.append(f"# Collisionmap: {height} filas x {width} columnas")
|
||||
lines.append("# Indices de colision (-1 = vacio, 1 = solido, 2 = plataforma)")
|
||||
lines.append("collisionmap:")
|
||||
|
||||
for row in collisionmap:
|
||||
row_str = ", ".join(str(v) for v in row)
|
||||
lines.append(f" - [{row_str}]")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def update_yaml_tilemap(yaml_path: Path, tilemap: list[list[int]], collisionmap: list[list[int]] | None, width: int, height: int) -> bool:
|
||||
"""
|
||||
Actualiza la seccion tilemap y collisionmap de un archivo YAML.
|
||||
|
||||
Returns:
|
||||
True si se actualizo correctamente, False si hubo error
|
||||
"""
|
||||
if not yaml_path.exists():
|
||||
print(f" ADVERTENCIA: {yaml_path} no existe, saltando...")
|
||||
return False
|
||||
|
||||
content = yaml_path.read_text(encoding='utf-8')
|
||||
|
||||
# Buscar donde empieza la seccion tilemap (incluyendo comentarios previos)
|
||||
# Capturar cualquier bloque de comentarios seguidos justo antes de tilemap:
|
||||
tilemap_pattern = re.compile(
|
||||
r'(#[^\n]*\n)*' # Cualquier comentario antes de tilemap
|
||||
r'tilemap:\s*\n' # Inicio de tilemap
|
||||
r'( - \[.*\]\n?)+' # Filas del tilemap
|
||||
)
|
||||
|
||||
match = tilemap_pattern.search(content)
|
||||
if not match:
|
||||
print(f" ERROR: No se encontro seccion tilemap en {yaml_path}")
|
||||
return False
|
||||
|
||||
# Verificar que capturamos comentarios relacionados con tilemap
|
||||
# Si el match incluye comentarios no relacionados, ajustar el inicio
|
||||
matched_text = match.group(0)
|
||||
lines = matched_text.split('\n')
|
||||
|
||||
# Buscar donde empiezan los comentarios de tilemap (los que queremos reemplazar)
|
||||
start_idx = 0
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith('#') and ('tilemap' in line.lower() or 'indice' in line.lower() or 'tile' in line.lower()):
|
||||
start_idx = i
|
||||
break
|
||||
elif line.startswith('tilemap:'):
|
||||
start_idx = i
|
||||
break
|
||||
|
||||
# Ajustar el inicio del match si hay comentarios que no queremos eliminar
|
||||
if start_idx > 0:
|
||||
skip_chars = sum(len(lines[i]) + 1 for i in range(start_idx))
|
||||
actual_start = match.start() + skip_chars
|
||||
else:
|
||||
actual_start = match.start()
|
||||
|
||||
# Generar nuevo tilemap
|
||||
new_tilemap = format_tilemap_yaml(tilemap, width, height)
|
||||
|
||||
# Reemplazar desde actual_start (que puede ser ajustado si hay comentarios no relacionados)
|
||||
new_content = content[:actual_start] + new_tilemap + "\n" + content[match.end():]
|
||||
|
||||
# Procesar collisionmap si existe
|
||||
if collisionmap is not None:
|
||||
new_collisionmap = format_collisionmap_yaml(collisionmap, width, height)
|
||||
|
||||
# Buscar si ya existe seccion collisionmap
|
||||
collisionmap_pattern = re.compile(
|
||||
r'(#[^\n]*\n)*' # Cualquier comentario antes de collisionmap
|
||||
r'collisionmap:\s*\n' # Inicio de collisionmap
|
||||
r'( - \[.*\]\n?)+' # Filas del collisionmap
|
||||
)
|
||||
|
||||
collision_match = collisionmap_pattern.search(new_content)
|
||||
if collision_match:
|
||||
# Reemplazar collisionmap existente
|
||||
collision_matched_text = collision_match.group(0)
|
||||
collision_lines = collision_matched_text.split('\n')
|
||||
|
||||
# Buscar donde empiezan los comentarios de collisionmap
|
||||
collision_start_idx = 0
|
||||
for i, line in enumerate(collision_lines):
|
||||
if line.startswith('#') and ('collision' in line.lower()):
|
||||
collision_start_idx = i
|
||||
break
|
||||
elif line.startswith('collisionmap:'):
|
||||
collision_start_idx = i
|
||||
break
|
||||
|
||||
if collision_start_idx > 0:
|
||||
skip_chars = sum(len(collision_lines[i]) + 1 for i in range(collision_start_idx))
|
||||
collision_actual_start = collision_match.start() + skip_chars
|
||||
else:
|
||||
collision_actual_start = collision_match.start()
|
||||
|
||||
new_content = new_content[:collision_actual_start] + new_collisionmap + "\n" + new_content[collision_match.end():]
|
||||
else:
|
||||
# Insertar collisionmap despues del tilemap
|
||||
# Buscar el fin del tilemap recien insertado
|
||||
tilemap_end_pattern = re.compile(r'tilemap:\s*\n( - \[.*\]\n?)+')
|
||||
tilemap_end_match = tilemap_end_pattern.search(new_content)
|
||||
if tilemap_end_match:
|
||||
insert_pos = tilemap_end_match.end()
|
||||
new_content = new_content[:insert_pos] + "\n" + new_collisionmap + "\n" + new_content[insert_pos:]
|
||||
|
||||
yaml_path.write_text(new_content, encoding='utf-8')
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
# Directorio de habitaciones
|
||||
script_dir = Path(__file__).parent
|
||||
room_dir = script_dir.parent / "data" / "room"
|
||||
|
||||
if not room_dir.exists():
|
||||
print(f"ERROR: No se encontro directorio {room_dir}")
|
||||
return 1
|
||||
|
||||
# Buscar archivos TMX
|
||||
tmx_files = sorted(room_dir.glob("*.tmx"))
|
||||
|
||||
if not tmx_files:
|
||||
print("No se encontraron archivos .tmx en data/room/")
|
||||
return 0
|
||||
|
||||
print(f"Encontrados {len(tmx_files)} archivos TMX")
|
||||
print()
|
||||
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
|
||||
for tmx_path in tmx_files:
|
||||
yaml_path = tmx_path.with_suffix('.yaml')
|
||||
print(f"Procesando: {tmx_path.name} -> {yaml_path.name}")
|
||||
|
||||
try:
|
||||
tilemap, collisionmap, width, height = parse_tmx(tmx_path)
|
||||
print(f" TMX: {width}x{height} tiles")
|
||||
if collisionmap is not None:
|
||||
print(f" Collisionmap detectado")
|
||||
|
||||
if update_yaml_tilemap(yaml_path, tilemap, collisionmap, width, height):
|
||||
print(f" OK: Tilemap actualizado")
|
||||
if collisionmap is not None:
|
||||
print(f" OK: Collisionmap actualizado")
|
||||
success_count += 1
|
||||
else:
|
||||
error_count += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
error_count += 1
|
||||
|
||||
print()
|
||||
print(f"Completado: {success_count} exitosos, {error_count} errores")
|
||||
return 0 if error_count == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
Reference in New Issue
Block a user