Post

Hilfreiche Scripts für die C++/Qt Entwicklung, Code Reviews und Quality Assurance

Hilfreiche Scripts für die C++/Qt Entwicklung, Code Reviews und Quality Assurance

Ein Skript zum Sortieren der SOURCES und HEADER Dateinamen in QMake pro Dateien

Um das Mergen von Qt-Code mit pro-Dateien zu vereinfachen, kann man die Einträge in den SOURCES und HEADER-Abschnitten von pro-Dateien vorher sortieren, dann sieht man besser, welche Dateien hinzugekommen sind oder entfernt wurden.

Das Skript sort_filenames_in_pro_files.py erwartet Tab-Zeichen als Einrückung in den pro-Dateien.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python3

# sort_filenames_in_pro_files.py
# Copyright 2025 Andreas Nicolai <andreas.nicolai -[at]- gmx.net>
#
# Script to automatically sort entries in SOURCES and HEADER sections of all 
# qmake pro files below the given top-level directory.
#
# Syntax: sort_filenames_in_pro_files.py [<directory>]
#
#         <directory> is the top-level directory, below which pro files
#         are searched for (recursively). If missing, the current working
#         directory will be used.

import os
import re
from pathlib import Path

def find_pro_files(root_dir):
	pro_files = []
	for dirpath, dirnames, filenames in os.walk(root_dir):
		for filename in filenames:
			if filename.endswith('.pro'):
				full_path = os.path.join(dirpath, filename)
				pro_files.append(full_path)
	return pro_files

def process_multiline_section(lines, start_idx):
	"""Extract, sort, and reformat a multiline += section."""
	section_lines = []
	idx = start_idx + 1
	while idx < len(lines):
		line = lines[idx].rstrip()
		if not line or line.startswith("#"):
			break
		if re.match(r'^\w+\s*\+=', line):  # another section starts
			break
		section_lines.append(line.strip(' \\'))
		idx += 1

	# Sort and reformat with backslashes
	section_lines = sorted(section_lines)
	for i in range(len(section_lines) - 1):
		section_lines[i] += ' \\\n'
	if len(section_lines) > 0:
		section_lines[-1] += '\n'

	return section_lines, idx

def process_file(filepath):
	with open(filepath, 'r', encoding='utf-8') as f:
		lines = f.readlines()

	i = 0
	while i < len(lines):
		line = lines[i].strip()
		if line.startswith('SOURCES +='):
			sorted_section, end_idx = process_multiline_section(lines, i)
			lines = lines[:i+1] + sorted_section + lines[end_idx:]
			i = end_idx
		elif line.startswith('HEADERS +='):
			sorted_section, end_idx = process_multiline_section(lines, i)
			lines = lines[:i+1] + sorted_section + lines[end_idx:]
			i = end_idx
		else:
			i += 1

	with open(filepath, 'w', encoding='utf-8') as f:
		f.writelines(lines)



if __name__ == "__main__":
	import argparse

	parser = argparse.ArgumentParser(
		description="Sort filenames in all .pro files with a directory tree.")
	parser.add_argument("directory", nargs="?", default=os.getcwd(), 
		help="Root directory to scan (default: current working directory)")

	args = parser.parse_args()
	pro_files = find_pro_files(args.directory)

	print(f"\nProcessing .pro files below  '{args.directory}':")
	for file in pro_files:
		print(file)
		process_file(file)

Ausgeführt wird es wie folgt:

1
2
3
4
5
# Suche unterhalb von '/path/to/source/code'
> python sort_filenames_in_pro_files.py /path/to/source/code

# oder suche unterhalb des aktuellen Arbeitsverzeichnisses
> python sort_filenames_in_pro_files.py

Coding Style: Konsistente Include Guards in Header-Dateien

Entsprechend der Coding-Guidelines ist es sinnvoll, in C++ Header-Dateien Include-Guards nach einheitlichem Schema zu verwenden.

Motivation

Bspw. für eine Klasse MeineKleineKlasse in der Header-Datei MeineKleineKlasse.h:

1
2
3
4
5
6
7
8
9
#ifndef MeineKleineKlasseH
#define MeineKleineKlasseH

class MeineKleineKlasse {
	// ...
};

#endif //  MeineKleineKlasseH

oder entsprechend dem Qt-Standard:

1
2
3
4
5
6
7
8
9
#ifndef MEINEKLEINEKLASSE_H
#define MEINEKLEINEKLASSE_H

class MeineKleineKlasse {
	// ...
};

#endif //  MEINEKLEINEKLASSE_H

oder bei Verwendung eines Namespaces MEINS in der Header-Datei MEINS_MeineKleineKlasse.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef MEINS_MeineKleineKlasseH
#define MEINS_MeineKleineKlasseH

namespace MEINS {

class MeineKleineKlasse {
	// ...
};

} // namespace MEINS

#endif //  MEINS_MeineKleineKlasseH

Automatisieren mit Python-Script

Wenn man jetzt häufiger mal neue Klassen erstellt (entweder mittels Qt Creator oder als Kopie von existierenden Dateien), sollten die Kommentare sinnvoll angepasst werden. Wenn man das häufiger mal vergisst, kann man mein kleines Python-Script nutzen fix_header_guards.py, um hier automatisch folgendes zu machen:

Bei Header-Dateien:

  • ‘namespace’ finden (falls deklariert) und Namen merken
  • ‘class’ finden und Namen merken, falls nicht gefunden
  • falls Klasse gefunden, Header Guard aus Namespace + Klassenname zusammenbauen und im Quelltext ersetzen
  • Warnen, falls Dateiname nicht dem Namensschema entspricht
  • Lizenz-Header einfügen (optional)

Bei Cpp-Dateien:

  • Lizenz-Header einfügen (optional)

Die Funktionen headerGuard(className, namespaceName) und baseName(className, namespaceName) können entsprechend eigener Vorlieben/Coding-Styles angepasst werden.

Ausführen des Scripts

Syntax: python fix_header_guards.py [--license=/path/to/LICENSE] /path/to/headerfiles

1
2
# Header-Guards für alle Dateien im aktuellen Verzeichnis aktualisieren
> python fix_header_guards.py .
This post is licensed under CC BY 4.0 by the author.