Skip to content

Commit 5f98f79

Browse files
Merge branch 'master' into fix-vulkan-iGPU-llvmpipe-selection
2 parents 436a995 + 5a36dbe commit 5f98f79

5 files changed

Lines changed: 249 additions & 22 deletions

File tree

.github/workflows/build-apple.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ jobs:
6161
with:
6262
platform: ${{ matrix.platform }}
6363

64+
- name: Test Build Scripts
65+
if: success()
66+
shell: bash
67+
run: |
68+
# Test File2Include utility
69+
cd "${{ github.workspace }}/BuildTools/File2Include"
70+
python -m unittest tests.py
71+
6472
- name: Configure CMake
6573
if: success()
6674
uses: DiligentGraphics/github-action/configure-cmake@v10

.github/workflows/build-linux.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ jobs:
9090
platform: Linux
9191
cmake-generator: ${{ matrix.cmake_generator }}
9292

93+
- name: Test Build Scripts
94+
if: success()
95+
shell: bash
96+
run: |
97+
# Test File2Include utility
98+
cd "${{ github.workspace }}/BuildTools/File2Include"
99+
python -m unittest tests.py
100+
93101
- name: Configure CMake
94102
if: success()
95103
uses: DiligentGraphics/github-action/configure-cmake@v10

.github/workflows/build-windows.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ jobs:
8383
cmake-generator: ${{ matrix.cmake_generator }}
8484
ninja-vs-arch: ${{ matrix.toolset }}
8585

86+
- name: Test Build Scripts
87+
if: success()
88+
shell: bash
89+
run: |
90+
# Test File2Include utility
91+
cd "${{ github.workspace }}/BuildTools/File2Include"
92+
python -m unittest tests.py
93+
8694
- name: Configure CMake
8795
if: success()
8896
uses: DiligentGraphics/github-action/configure-cmake@v10

BuildTools/File2Include/script.py

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,37 @@ def strip_c_comments(text):
4141
line_has_comment = False
4242
line_has_noncomment_code = False
4343

44+
block_comment_buf = []
45+
line_comment_buf = []
46+
4447
def append_char(c):
4548
nonlocal line_has_noncomment_code
4649
current_line.append(c)
4750
if not c.isspace():
4851
line_has_noncomment_code = True
4952

53+
def append_text(s):
54+
for ch in s:
55+
append_char(ch)
56+
57+
def should_preserve_block_comment(comment_text):
58+
inner = comment_text[2:-2].strip()
59+
return inner.startswith("format")
60+
61+
def should_preserve_line_comment(comment_text):
62+
inner = comment_text[2:].strip()
63+
return inner.startswith("format")
64+
5065
def flush_line(add_newline):
5166
nonlocal current_line, line_has_comment, line_has_noncomment_code
5267

53-
# Remove lines that contain only a comment (possibly with whitespace around it)
5468
if not (line_has_comment and not line_has_noncomment_code and "".join(current_line).strip() == ""):
5569
result.extend(current_line)
5670
if add_newline:
5771
result.append("\n")
5872

5973
current_line = []
6074
line_has_noncomment_code = False
61-
62-
# If we are still inside a block comment, the next physical line is also comment-only so far
6375
line_has_comment = in_block_comment
6476

6577
i = 0
@@ -71,19 +83,36 @@ def flush_line(add_newline):
7183

7284
if in_line_comment:
7385
if c == "\n":
86+
comment_text = "".join(line_comment_buf)
87+
if should_preserve_line_comment(comment_text):
88+
append_text(comment_text)
89+
90+
line_comment_buf = []
7491
in_line_comment = False
7592
flush_line(True)
93+
i += 1
94+
continue
95+
96+
line_comment_buf.append(c)
7697
i += 1
7798
continue
7899

79100
if in_block_comment:
101+
block_comment_buf.append(c)
102+
80103
if c == "*" and nxt == "/":
104+
block_comment_buf.append("/")
105+
comment_text = "".join(block_comment_buf)
106+
81107
in_block_comment = False
108+
block_comment_buf = []
109+
110+
if should_preserve_block_comment(comment_text):
111+
append_text(comment_text)
112+
82113
i += 2
83114
continue
84115

85-
if c == "\n":
86-
flush_line(True)
87116
i += 1
88117
continue
89118

@@ -112,12 +141,14 @@ def flush_line(add_newline):
112141
if c == "/" and nxt == "/":
113142
line_has_comment = True
114143
in_line_comment = True
144+
line_comment_buf = ["//"]
115145
i += 2
116146
continue
117147

118148
if c == "/" and nxt == "*":
119149
line_has_comment = True
120150
in_block_comment = True
151+
block_comment_buf = ["/*"]
121152
i += 2
122153
continue
123154

@@ -141,13 +172,42 @@ def flush_line(add_newline):
141172
append_char(c)
142173
i += 1
143174

175+
if in_line_comment:
176+
comment_text = "".join(line_comment_buf)
177+
if should_preserve_line_comment(comment_text):
178+
append_text(comment_text)
179+
144180
if current_line:
145181
if not (line_has_comment and not line_has_noncomment_code and "".join(current_line).strip() == ""):
146182
result.extend(current_line)
147183

148184
return "".join(result)
149185

150186

187+
def prepare_content(text, strip_comments=False):
188+
if strip_comments:
189+
return strip_c_comments(text)
190+
return text
191+
192+
193+
def convert_to_string_lines(text):
194+
special_chars = "'\"\\"
195+
output = []
196+
197+
for line in text.splitlines():
198+
quoted = ['"']
199+
200+
for c in line.rstrip():
201+
if c in special_chars:
202+
quoted.append("\\")
203+
quoted.append(c)
204+
205+
quoted.append('\\n"\n')
206+
output.append("".join(quoted))
207+
208+
return "".join(output)
209+
210+
151211
def main():
152212
parser = argparse.ArgumentParser(
153213
description="Convert a file into quoted string lines."
@@ -167,23 +227,9 @@ def main():
167227
raise ValueError("Source and destination files must be different")
168228

169229
with open(args.src, "r") as src_file, open(args.dst, "w") as dst_file:
170-
special_chars = "\'\"\\"
171-
172-
if args.strip_comments:
173-
content = strip_c_comments(src_file.read())
174-
src_iter = io.StringIO(content)
175-
else:
176-
src_iter = src_file
177-
178-
for line in src_iter:
179-
dst_file.write('\"')
180-
181-
for i, c in enumerate(line.rstrip()):
182-
if special_chars.find(c) != -1:
183-
dst_file.write('\\')
184-
dst_file.write(c)
185-
186-
dst_file.write('\\n\"\n')
230+
content = src_file.read()
231+
content = prepare_content(content, args.strip_comments)
232+
dst_file.write(convert_to_string_lines(content))
187233

188234
print("File2String: successfully converted {} to {}".format(args.src, args.dst))
189235

BuildTools/File2Include/tests.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import unittest
2+
3+
from script import strip_c_comments, convert_to_string_lines
4+
5+
6+
class StripCCommentsTests(unittest.TestCase):
7+
def test_keeps_plain_code(self):
8+
src = "int x = 1;\nfloat y = 2;\n"
9+
self.assertEqual(strip_c_comments(src), src)
10+
11+
def test_removes_line_comment(self):
12+
src = "int x = 1; // comment\n"
13+
self.assertEqual(strip_c_comments(src), "int x = 1; \n")
14+
15+
def test_removes_block_comment(self):
16+
src = "int /* comment */ x = 1;\n"
17+
self.assertEqual(strip_c_comments(src), "int x = 1;\n")
18+
19+
def test_removes_multiline_block_comment(self):
20+
src = "int x = 1; /* comment\nstill comment */\nfloat y = 2;\n"
21+
self.assertEqual(strip_c_comments(src), "int x = 1; \nfloat y = 2;\n")
22+
23+
def test_removes_comment_only_line(self):
24+
src = "int x = 1;\n// comment only\nfloat y = 2;\n"
25+
self.assertEqual(strip_c_comments(src), "int x = 1;\nfloat y = 2;\n")
26+
27+
def test_removes_whitespace_and_comment_only_line(self):
28+
src = "int x = 1;\n // comment only\nfloat y = 2;\n"
29+
self.assertEqual(strip_c_comments(src), "int x = 1;\nfloat y = 2;\n")
30+
31+
def test_preserves_comment_like_text_in_string(self):
32+
src = 'const char* s = "not // comment and not /* comment */";\n'
33+
self.assertEqual(strip_c_comments(src), src)
34+
35+
def test_preserves_comment_like_text_in_char_literal(self):
36+
src = "char c = '/'; // real comment\n"
37+
self.assertEqual(strip_c_comments(src), "char c = '/'; \n")
38+
39+
def test_preserves_escaped_quotes_in_string(self):
40+
src = 'const char* s = "\\"quoted\\" // still string"; // comment\n'
41+
expected = 'const char* s = "\\"quoted\\" // still string"; \n'
42+
self.assertEqual(strip_c_comments(src), expected)
43+
44+
def test_preserves_format_block_comment(self):
45+
src = "RWTexture2D<float /* format = r32f */ >\n"
46+
self.assertEqual(strip_c_comments(src), src)
47+
48+
def test_preserves_format_line_comment(self):
49+
src = "RWTexture2D<float // format = r32f\n>\n"
50+
self.assertEqual(strip_c_comments(src), src)
51+
52+
def test_mixed_preserved_and_normal_comments(self):
53+
src = (
54+
"RWTexture2D<float /* format = r32f */ > // remove this\n"
55+
"int x = 1; /* remove this */\n"
56+
)
57+
expected = (
58+
"RWTexture2D<float /* format = r32f */ > \n"
59+
"int x = 1; \n"
60+
)
61+
self.assertEqual(strip_c_comments(src), expected)
62+
63+
def test_escaped_backslash_at_end_of_string(self):
64+
# A backslash escaping another backslash shouldn't escape the quote
65+
src = 'const char* s = "backslash\\\\"; // comment\n'
66+
expected = 'const char* s = "backslash\\\\"; \n'
67+
self.assertEqual(strip_c_comments(src), expected)
68+
69+
def test_comment_start_inside_char_literal(self):
70+
# Testing if it handles /* inside a character literal correctly
71+
src = "char a = '/'; char b = '*';\n"
72+
self.assertEqual(strip_c_comments(src), src)
73+
74+
def test_block_comment_preserved_with_internal_stars(self):
75+
# Ensure the 'format' check doesn't fail if there are internal decorators
76+
src = "/* format: r32f ******* */\n"
77+
self.assertEqual(strip_c_comments(src), src)
78+
79+
def test_multiple_block_comments_one_line(self):
80+
src = "/* remove */ code(); /* format: preserve */\n"
81+
expected = " code(); /* format: preserve */\n"
82+
self.assertEqual(strip_c_comments(src), expected)
83+
84+
def test_multiline_block_comment_inline_does_not_insert_newline(self):
85+
src = "x /* comment\nstill comment */ y\n"
86+
self.assertEqual(strip_c_comments(src), "x y\n")
87+
88+
def test_multiline_preserved_block_comment_stays_inline(self):
89+
src = "x /* format = r32f\nstill format */ y\n"
90+
self.assertEqual(
91+
strip_c_comments(src),
92+
"x /* format = r32f\nstill format */ y\n",
93+
)
94+
95+
def test_removes_line_comment_at_eof(self):
96+
src = "int x = 1; // comment"
97+
self.assertEqual(strip_c_comments(src), "int x = 1; ")
98+
99+
def test_preserves_format_line_comment_at_eof(self):
100+
src = "x // format = r32f"
101+
self.assertEqual(strip_c_comments(src), "x // format = r32f")
102+
103+
class ConvertToStringLinesTests(unittest.TestCase):
104+
def test_converts_single_line(self):
105+
src = "hello\n"
106+
self.assertEqual(convert_to_string_lines(src), '"hello\\n"\n')
107+
108+
def test_converts_multiple_lines(self):
109+
src = "hello\nworld\n"
110+
self.assertEqual(convert_to_string_lines(src), '"hello\\n"\n"world\\n"\n')
111+
112+
def test_escapes_double_quotes(self):
113+
src = '"abc"\n'
114+
self.assertEqual(convert_to_string_lines(src), '"\\"abc\\"\\n"\n')
115+
116+
def test_escapes_single_quotes(self):
117+
src = "'a'\n"
118+
self.assertEqual(convert_to_string_lines(src), '"\\\'a\\\'\\n"\n')
119+
120+
def test_escapes_backslashes(self):
121+
src = "C:\\temp\\file\n"
122+
self.assertEqual(convert_to_string_lines(src), '"C:\\\\temp\\\\file\\n"\n')
123+
124+
def test_preserves_empty_line(self):
125+
src = "\n"
126+
self.assertEqual(convert_to_string_lines(src), '"\\n"\n')
127+
128+
def test_handles_text_without_trailing_newline(self):
129+
src = "hello"
130+
self.assertEqual(convert_to_string_lines(src), '"hello\\n"\n')
131+
132+
def test_handles_text_with_trailing_spaces(self):
133+
src = "hello "
134+
self.assertEqual(convert_to_string_lines(src), '"hello\\n"\n')
135+
136+
def test_strip_then_convert_integration(self):
137+
src = 'int x = 1; // comment\nconst char* s = "a";\n'
138+
stripped = strip_c_comments(src)
139+
expected = (
140+
'"int x = 1;\\n"\n'
141+
'"const char* s = \\"a\\";\\n"\n'
142+
)
143+
self.assertEqual(convert_to_string_lines(stripped), expected)
144+
145+
def test_empty_input(self):
146+
self.assertEqual(convert_to_string_lines(""), "")
147+
148+
def test_preserves_blank_line_in_middle(self):
149+
src = "a\n\nb\n"
150+
self.assertEqual(convert_to_string_lines(src), '"a\\n"\n"\\n"\n"b\\n"\n')
151+
152+
def test_trims_trailing_tabs_too(self):
153+
src = "hello\t\t\n"
154+
self.assertEqual(convert_to_string_lines(src), '"hello\\n"\n')
155+
156+
if __name__ == "__main__":
157+
unittest.main()

0 commit comments

Comments
 (0)