Skip to content

Commit 558635e

Browse files
authored
Refactor line_endings.py. NFC (#20735)
Most of the code in this file is used only for testing and is not part of emscripten proper, so move that code into `test/line_endings.py`. Move the remaining utility functions to `tools/utils.py`. Update `write_file` utility so it can write a file with the correct line endings. This simplifies the callers who want to write a file with specific line endings.
1 parent 3edcc1b commit 558635e

File tree

10 files changed

+68
-76
lines changed

10 files changed

+68
-76
lines changed

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ See docs/process.md for more on how version tagging works.
2020

2121
4.0.4 (in development)
2222
----------------------
23+
- The `--output_eol` command line flag was renamed `--output-eol` for
24+
consistency with other flags. The old name continues to work as an alias.
25+
(#20735)
2326

2427
4.0.3 - 02/07/25
2528
----------------

docs/emcc.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -574,12 +574,12 @@ Options that are modified or new in *emcc* are listed below:
574574
[compile] Tells *emcc* to emit an object file which can then be
575575
linked with other object files to produce an executable.
576576

577-
"--output_eol windows|linux"
577+
"--output-eol windows|linux"
578578
[link] Specifies the line ending to generate for the text files
579-
that are outputted. If "--output_eol windows" is passed, the final
580-
output files will have Windows rn line endings in them. With "--
581-
output_eol linux", the final generated files will be written with
582-
Unix n line endings.
579+
that are outputted. If "--output-eol windows" is passed, the final
580+
output files will have Windows "\r\n" line endings in them. With "
581+
--output-eol linux", the final generated files will be written with
582+
Unix "\n" line endings.
583583

584584
"--cflags"
585585
[other] Prints out the flags "emcc" would pass to "clang" to

emcc.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
'--bind', '--closure', '--cpuprofiler', '--embed-file',
8383
'--emit-symbol-map', '--emrun', '--exclude-file', '--extern-post-js',
8484
'--extern-pre-js', '--ignore-dynamic-linking', '--js-library',
85-
'--js-transform', '--oformat', '--output_eol',
85+
'--js-transform', '--oformat', '--output_eol', '--output-eol',
8686
'--post-js', '--pre-js', '--preload-file', '--profiling-funcs',
8787
'--proxy-to-worker', '--shell-file', '--source-map-base',
8888
'--threadprofiler', '--use-preload-plugins'
@@ -1348,14 +1348,14 @@ def consume_arg_file():
13481348
exit_with_error('--default-obj-ext is no longer supported by emcc')
13491349
elif arg.startswith('-fsanitize=cfi'):
13501350
exit_with_error('emscripten does not currently support -fsanitize=cfi')
1351-
elif check_arg('--output_eol'):
1351+
elif check_arg('--output_eol') or check_arg('--output-eol'):
13521352
style = consume_arg()
13531353
if style.lower() == 'windows':
13541354
options.output_eol = '\r\n'
13551355
elif style.lower() == 'linux':
13561356
options.output_eol = '\n'
13571357
else:
1358-
exit_with_error(f'Invalid value "{style}" to --output_eol!')
1358+
exit_with_error(f'invalid value for --output-eol: `{style}`')
13591359
# Record PTHREADS setting because it controls whether --shared-memory is passed to lld
13601360
elif arg == '-pthread':
13611361
settings.PTHREADS = 1

site/source/docs/tools_reference/emcc.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,9 +566,9 @@ Options that are modified or new in *emcc* are listed below:
566566
[compile]
567567
Tells *emcc* to emit an object file which can then be linked with other object files to produce an executable.
568568

569-
``--output_eol windows|linux``
569+
``--output-eol windows|linux``
570570
[link]
571-
Specifies the line ending to generate for the text files that are outputted. If "--output_eol windows" is passed, the final output files will have Windows \r\n line endings in them. With "--output_eol linux", the final generated files will be written with Unix \n line endings.
571+
Specifies the line ending to generate for the text files that are outputted. If "--output-eol windows" is passed, the final output files will have Windows ``\r\n`` line endings in them. With "--output-eol linux", the final generated files will be written with Unix ``\n`` line endings.
572572

573573
``--cflags``
574574
[other]

test/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@
3333

3434
import clang_native
3535
import jsrun
36+
import line_endings
3637
from tools.shared import EMCC, EMXX, DEBUG, EMCONFIGURE, EMCMAKE
3738
from tools.shared import get_canonical_temp_dir, path_from_root
3839
from tools.utils import MACOS, WINDOWS, read_file, read_binary, write_binary, exit_with_error
3940
from tools.settings import COMPILE_TIME_SETTINGS
40-
from tools import shared, feature_matrix, line_endings, building, config, utils
41+
from tools import shared, feature_matrix, building, config, utils
4142

4243
logger = logging.getLogger('common')
4344

tools/line_endings.py renamed to test/line_endings.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,7 @@
77
import os
88
import sys
99

10-
11-
def convert_line_endings(text, from_eol, to_eol):
12-
if from_eol == to_eol:
13-
return text
14-
return text.replace(from_eol, to_eol)
15-
16-
17-
def convert_line_endings_in_file(filename, from_eol, to_eol):
18-
if from_eol == to_eol:
19-
return # No conversion needed
20-
21-
with open(filename, 'rb') as f:
22-
text = f.read()
23-
text = convert_line_endings(text, from_eol.encode(), to_eol.encode())
24-
with open(filename, 'wb') as f:
25-
f.write(text)
10+
from tools import utils
2611

2712

2813
def check_line_endings(filename, expect_only=None, print_errors=True, print_info=False):
@@ -38,8 +23,7 @@ def check_line_endings(filename, expect_only=None, print_errors=True, print_info
3823
print('File not found: ' + filename, file=sys.stderr)
3924
return 1
4025

41-
with open(filename, 'rb') as f:
42-
data = f.read()
26+
data = utils.read_binary(filename)
4327

4428
index = data.find(b"\r\r\n")
4529
if index != -1:
@@ -75,15 +59,15 @@ def check_line_endings(filename, expect_only=None, print_errors=True, print_info
7559
old_macos_line_ending_example = data[index - 50:index + 50].replace(b'\r', b'\\r').replace(b'\n', b'\\n')
7660
if print_errors:
7761
print('File \'' + filename + '\' contains OLD macOS line endings "\\r"', file=sys.stderr)
78-
print("Content around an OLD macOS line ending location: '" + old_macos_line_ending_example + "'", file=sys.stderr)
62+
print("Content around an OLD macOS line ending location: '" + old_macos_line_ending_example.decode('utf-8') + "'", file=sys.stderr)
7963
# We don't want to use the old macOS (9.x) line endings anywhere.
8064
return 1
8165

8266
if has_dos_line_endings and has_unix_line_endings:
8367
if print_errors:
8468
print('File \'' + filename + '\' contains both DOS "\\r\\n" and UNIX "\\n" line endings! (' + str(dos_line_ending_count) + ' DOS line endings, ' + str(unix_line_ending_count) + ' UNIX line endings)', file=sys.stderr)
85-
print("Content around a DOS line ending location: '" + dos_line_ending_example + "'", file=sys.stderr)
86-
print("Content around an UNIX line ending location: '" + unix_line_ending_example + "'", file=sys.stderr)
69+
print("Content around a DOS line ending location: '" + dos_line_ending_example.decode('utf-8') + "'", file=sys.stderr)
70+
print("Content around an UNIX line ending location: '" + unix_line_ending_example.decode('utf-8') + "'", file=sys.stderr)
8771
# Mixed line endings
8872
return 1
8973

@@ -96,13 +80,13 @@ def check_line_endings(filename, expect_only=None, print_errors=True, print_info
9680
if expect_only == '\n' and has_dos_line_endings:
9781
if print_errors:
9882
print('File \'' + filename + '\' contains DOS "\\r\\n" line endings! (' + str(dos_line_ending_count) + ' DOS line endings), but expected only UNIX line endings!', file=sys.stderr)
99-
print("Content around a DOS line ending location: '" + dos_line_ending_example + "'", file=sys.stderr)
83+
print("Content around a DOS line ending location: '" + dos_line_ending_example.decode('utf-8') + "'", file=sys.stderr)
10084
return 1 # DOS line endings, but expected UNIX
10185

10286
if expect_only == '\r\n' and has_unix_line_endings:
10387
if print_errors:
10488
print('File \'' + filename + '\' contains UNIX "\\n" line endings! (' + str(unix_line_ending_count) + ' UNIX line endings), but expected only DOS line endings!', file=sys.stderr)
105-
print("Content around a UNIX line ending location: '" + unix_line_ending_example + "'", file=sys.stderr)
89+
print("Content around a UNIX line ending location: '" + unix_line_ending_example.decode('utf-8') + "'", file=sys.stderr)
10690
return 1 # UNIX line endings, but expected DOS
10791

10892
return 0

test/test_other.py

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
import common
4747
import jsrun
4848
import clang_native
49-
from tools import line_endings
49+
import line_endings
5050
from tools import webassembly
5151
from tools.settings import settings
5252

@@ -8703,28 +8703,24 @@ def test_disable_inlining(self):
87038703
self.assertContained('foo', output)
87048704

87058705
@crossplatform
8706-
def test_output_eol(self):
8707-
for params in ([], ['--proxy-to-worker'], ['--proxy-to-worker', '-sWASM=0']):
8708-
for output_suffix in ('html', 'js'):
8709-
for eol in ('windows', 'linux'):
8710-
files = ['a.js']
8711-
if output_suffix == 'html':
8712-
files += ['a.html']
8713-
cmd = [EMCC, test_file('hello_world.c'), '-o', 'a.' + output_suffix, '--output_eol', eol] + params
8714-
self.run_process(cmd)
8715-
for f in files:
8716-
print(str(cmd) + ' ' + str(params) + ' ' + eol + ' ' + f)
8717-
self.assertExists(f)
8718-
if eol == 'linux':
8719-
expected_ending = '\n'
8720-
else:
8721-
expected_ending = '\r\n'
8722-
8723-
ret = line_endings.check_line_endings(f, expect_only=expected_ending)
8724-
self.assertEqual(ret, 0)
8706+
@parameterized({
8707+
'': ([],),
8708+
'proxy_to_worker': (['--proxy-to-worker'],),
8709+
'proxy_to_worker_wasm2js': (['--proxy-to-worker', '-sWASM=0'],),
8710+
})
8711+
def test_output_eol(self, params):
8712+
for eol in ('windows', 'linux'):
8713+
self.clear()
8714+
print('checking eol: ', eol)
8715+
self.run_process([EMCC, test_file('hello_world.c'), '-o', 'a.html', '--output-eol', eol] + params)
8716+
for f in ['a.html', 'a.js']:
8717+
self.assertExists(f)
8718+
if eol == 'linux':
8719+
expected_ending = '\n'
8720+
else:
8721+
expected_ending = '\r\n'
87258722

8726-
for f in files:
8727-
delete_file(f)
8723+
self.assertEqual(line_endings.check_line_endings(f, expect_only=expected_ending), 0, f'expected on ly {eol} line endingsn in {f}')
87288724

87298725
def test_binaryen_warn_mem(self):
87308726
# if user changes INITIAL_MEMORY at runtime, the wasm module may not accept the memory import if
@@ -8906,23 +8902,23 @@ def test_unoptimized_code_size(self):
89068902
# under control to a certain extent. This test allows us to track major
89078903
# changes to the size of the unoptimized and unminified code size.
89088904
# Run with `--rebase` when this test fails.
8909-
self.build(test_file('hello_world.c'), emcc_args=['-O0', '--output_eol=linux'])
8905+
self.build(test_file('hello_world.c'), emcc_args=['-O0', '--output-eol=linux'])
89108906
self.check_expected_size_in_file('wasm',
89118907
test_file('other/test_unoptimized_code_size.wasm.size'),
89128908
os.path.getsize('hello_world.wasm'))
89138909
self.check_expected_size_in_file('js',
89148910
test_file('other/test_unoptimized_code_size.js.size'),
89158911
os.path.getsize('hello_world.js'))
89168912

8917-
self.build(test_file('hello_world.c'), emcc_args=['-O0', '--output_eol=linux', '-sASSERTIONS=0'], output_basename='no_asserts')
8913+
self.build(test_file('hello_world.c'), emcc_args=['-O0', '--output-eol=linux', '-sASSERTIONS=0'], output_basename='no_asserts')
89188914
self.check_expected_size_in_file('wasm',
89198915
test_file('other/test_unoptimized_code_size_no_asserts.wasm.size'),
89208916
os.path.getsize('no_asserts.wasm'))
89218917
self.check_expected_size_in_file('js',
89228918
test_file('other/test_unoptimized_code_size_no_asserts.js.size'),
89238919
os.path.getsize('no_asserts.js'))
89248920

8925-
self.build(test_file('hello_world.c'), emcc_args=['-O0', '--output_eol=linux', '-sSTRICT'], output_basename='strict')
8921+
self.build(test_file('hello_world.c'), emcc_args=['-O0', '--output-eol=linux', '-sSTRICT'], output_basename='strict')
89268922
self.check_expected_size_in_file('wasm',
89278923
test_file('other/test_unoptimized_code_size_strict.wasm.size'),
89288924
os.path.getsize('strict.wasm'))
@@ -8939,7 +8935,7 @@ def run_codesize_test(self, filename, args=[], expected_exists=[], expected_not_
89398935
expected_basename = test_file('other/codesize', self.id().split('.')[-1])
89408936

89418937
# Run once without closure and parse output to find wasmImports
8942-
build_cmd = [compiler_for(filename), filename, '--output_eol=linux', '--emit-minification-map=minify.map'] + args + self.get_emcc_args()
8938+
build_cmd = [compiler_for(filename), filename, '--output-eol=linux', '--emit-minification-map=minify.map'] + args + self.get_emcc_args()
89438939
self.run_process(build_cmd + ['-g2'])
89448940
# find the imports we send from JS
89458941
# TODO(sbc): Find a way to do that that doesn't depend on internal details of
@@ -11411,7 +11407,7 @@ def test_minimal_runtime_code_size(self, test_name, js, compare_js_output=False)
1141111407
'-sGL_ENABLE_GET_PROC_ADDRESS=0',
1141211408
'-sNO_FILESYSTEM',
1141311409
'-sSTRICT',
11414-
'--output_eol', 'linux',
11410+
'--output-eol', 'linux',
1141511411
'-Oz',
1141611412
'--closure=1',
1141711413
'-DNDEBUG',
@@ -11962,7 +11958,7 @@ def test_main_reads_params(self):
1196211958

1196311959
def test_INCOMING_MODULE_JS_API(self):
1196411960
def test(args):
11965-
self.run_process([EMCC, test_file('hello_world.c'), '-O3', '--closure=1', '-sENVIRONMENT=node,shell', '--output_eol=linux'] + args)
11961+
self.run_process([EMCC, test_file('hello_world.c'), '-O3', '--closure=1', '-sENVIRONMENT=node,shell', '--output-eol=linux'] + args)
1196611962
for engine in config.JS_ENGINES:
1196711963
self.assertContained('hello, world!', self.run_js('a.out.js', engine=engine))
1196811964
return os.path.getsize('a.out.js')

tools/link.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@
4141
from .settings import settings, default_setting, user_settings, JS_ONLY_SETTINGS, DEPRECATED_SETTINGS
4242
from .minimal_runtime_shell import generate_minimal_runtime_html
4343

44-
import tools.line_endings
45-
4644
logger = logging.getLogger('link')
4745

4846
DEFAULT_SHELL_HTML = utils.path_from_root('src/shell.html')
@@ -2090,7 +2088,7 @@ def create_worker_file(input_file, target_dir, output_file, options):
20902088
contents = building.acorn_optimizer(output_file, ['--minify-whitespace'], return_output=True, worker_js=True)
20912089
write_file(output_file, contents)
20922090

2093-
tools.line_endings.convert_line_endings_in_file(output_file, os.linesep, options.output_eol)
2091+
utils.convert_line_endings_in_file(output_file, options.output_eol)
20942092

20952093

20962094
@ToolchainProfiler.profile_block('final emitting')
@@ -2160,14 +2158,14 @@ def phase_final_emitting(options, target, js_target, wasm_target):
21602158
generate_html(target, options, js_target, target_basename,
21612159
wasm_target)
21622160
elif settings.PROXY_TO_WORKER:
2163-
generate_worker_js(target, js_target, target_basename)
2161+
generate_worker_js(target, options, js_target, target_basename)
21642162

21652163
if settings.SPLIT_MODULE:
21662164
diagnostics.warning('experimental', 'the SPLIT_MODULE setting is experimental and subject to change')
21672165
do_split_module(wasm_target, options)
21682166

21692167
if not settings.SINGLE_FILE:
2170-
tools.line_endings.convert_line_endings_in_file(js_target, os.linesep, options.output_eol)
2168+
utils.convert_line_endings_in_file(js_target, options.output_eol)
21712169

21722170
if options.executable:
21732171
make_js_executable(js_target)
@@ -2672,10 +2670,10 @@ def generate_html(target, options, js_target, target_basename, wasm_target):
26722670
if settings.MINIFY_HTML and (settings.OPT_LEVEL >= 1 or settings.SHRINK_LEVEL >= 1):
26732671
minify_html(target)
26742672

2675-
tools.line_endings.convert_line_endings_in_file(target, os.linesep, options.output_eol)
2673+
utils.convert_line_endings_in_file(target, options.output_eol)
26762674

26772675

2678-
def generate_worker_js(target, js_target, target_basename):
2676+
def generate_worker_js(target, options, js_target, target_basename):
26792677
if settings.SINGLE_FILE:
26802678
# compiler output is embedded as base64 data URL
26812679
proxy_worker_filename = get_subresource_location_js(js_target)
@@ -2686,7 +2684,7 @@ def generate_worker_js(target, js_target, target_basename):
26862684
proxy_worker_filename = (settings.PROXY_TO_WORKER_FILENAME or worker_target_basename) + '.js'
26872685

26882686
target_contents = worker_js_script(proxy_worker_filename)
2689-
write_file(target, target_contents)
2687+
utils.write_file(target, target_contents, options.output_eol)
26902688

26912689

26922690
def worker_js_script(proxy_worker_filename):

tools/minimal_runtime_shell.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
sys.path.insert(0, __rootdir__)
99

1010
from . import shared
11-
from . import line_endings
1211
from . import utils
1312
from . import feature_matrix
1413
from .settings import settings
@@ -211,5 +210,4 @@ def generate_minimal_runtime_html(target, options, js_target, target_basename):
211210
else:
212211
js_contents = ''
213212
shell = shell.replace('{{{ JS_CONTENTS_IN_SINGLE_FILE_BUILD }}}', js_contents)
214-
shell = line_endings.convert_line_endings(shell, '\n', options.output_eol)
215-
utils.write_file(target, shell)
213+
utils.write_file(target, shell, options.output_eol)

tools/utils.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ def removeprefix(string, prefix):
4747
return string
4848

4949

50+
def convert_line_endings_in_file(filename, to_eol):
51+
if to_eol == os.linesep:
52+
return # No conversion needed
53+
54+
text = read_file(filename)
55+
write_file(filename, text, line_endings=to_eol)
56+
57+
5058
def read_file(file_path):
5159
"""Read from a file opened in text mode"""
5260
with open(file_path, encoding='utf-8') as fh:
@@ -59,10 +67,14 @@ def read_binary(file_path):
5967
return fh.read()
6068

6169

62-
def write_file(file_path, text):
70+
def write_file(file_path, text, line_endings=None):
6371
"""Write to a file opened in text mode"""
64-
with open(file_path, 'w', encoding='utf-8') as fh:
65-
fh.write(text)
72+
if line_endings and line_endings != os.linesep:
73+
text = text.replace('\n', line_endings)
74+
write_binary(file_path, text.encode('utf-8'))
75+
else:
76+
with open(file_path, 'w', encoding='utf-8') as fh:
77+
fh.write(text)
6678

6779

6880
def write_binary(file_path, contents):

0 commit comments

Comments
 (0)