diff --git a/CHANGES.txt b/CHANGES.txt index d4ad973a0b..2977fa8c7d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -23,6 +23,15 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER calls dunder method __call__. Invoke instance directly." - Python 3.9 dropped the alias base64.decodestring, deprecated since 3.1. Only used in msvs.py. Use base64.decodebytes instead. + - The yacc tool now understands the bison behavior of --header, + --defines and --graph being called without option-argument as being + synonyms for -d (first two) and -g. -H also recognized as a synonym + for -d. Default value for $YACCVCGFILESUFFIX changed to '.gv' + to match current bison default (since bison 3.8). The graph file name + (-g) is now generated relative to the requested target file name, + not to the source file name, to match actual current behavior (only + affects if target explicitly requested with a different base name + than source). Docs updated. Fixes #4326 and #4327. RELEASE 4.5.2 - Sun, 21 Mar 2023 14:08:29 -0700 diff --git a/SCons/Tool/Tool.xml b/SCons/Tool/Tool.xml index de71efb6b7..d25d7a5efc 100644 --- a/SCons/Tool/Tool.xml +++ b/SCons/Tool/Tool.xml @@ -37,10 +37,19 @@ Example: # builds foo.c -env.CFile(target = 'foo.c', source = 'foo.l') +env.CFile(target='foo.c', source='foo.l') + # builds bar.c -env.CFile(target = 'bar', source = 'bar.y') +env.CFile(target='bar', source='bar.y') + + +Note that for yacc files, +the output file name is derived from target, +or from the source file name if a target is not specified; +the traditional yacc default name +y.tab.c is not used. + @@ -59,10 +68,19 @@ Example: # builds foo.cc -env.CXXFile(target = 'foo.cc', source = 'foo.ll') +env.CXXFile(target='foo.cc', source='foo.ll') + # builds bar.cc -env.CXXFile(target = 'bar', source = 'bar.yy') +env.CXXFile(target='bar', source='bar.yy') + + +Note that for yacc files, +the output file name is derived from target, +or from the source file name if a target is not specified; +the traditional yacc default name +y.tab.cc is not used. + diff --git a/SCons/Tool/yacc.py b/SCons/Tool/yacc.py index 67ecd8678d..d408e621de 100644 --- a/SCons/Tool/yacc.py +++ b/SCons/Tool/yacc.py @@ -23,8 +23,11 @@ """Tool-specific initialization for yacc. -This tool should support multiple yacc implementations, -but is in actuality biased towards GNU Bison. +This tool should support multiple yacc implementations, but is in actuality +biased towards GNU Bison. In particular, it forces the output file name (thus +avoiding the default convention of y.tab.c or foo.tab.c), so the tool *must* +support the -o option, which pure POSIX yacc does not. byacc should be okay +as an alternative to bison. There normally shouldn't be any need to import this module directly. It will usually be imported through the generic SCons.Tool.Tool() @@ -64,15 +67,17 @@ def _yaccEmitter(target, source, env, ysuf, hsuf) -> tuple: target = [targetBase + ".m"] # the extension is ".m". # If -d is specified on the command line, yacc will emit a .h - # or .hpp file with the same name as the .c or .cpp output file. - if '-d' in flags: + # or .hpp file with the same base name as the .c or .cpp output file. + # if '-d' in flags: # add bison options -H, --header, --defines (obsolete) + if "-d" in flags or "-H" in flags or "--header" in flags or "--defines" in flags: target.append(targetBase + env.subst(hsuf, target=target, source=source)) - # If -g is specified on the command line, yacc will emit a .vcg - # file with the same base name as the .y, .yacc, .ym or .yy file. - if "-g" in flags: - base, ext = os.path.splitext(to_String(source[0])) - target.append(base + env.subst("$YACCVCGFILESUFFIX")) + # If -g is specified on the command line, yacc will emit a graph + # file with the same base name as the .c or .cpp output file. + # TODO: should this be handled like -v? i.e. a side effect, not target + # if "-g" in flags: # add bison option --graph + if "-g" in flags or "--graph" in flags: + target.append(targetBase + env.subst("$YACCVCGFILESUFFIX")) # If -v is specified yacc will create the output debug file # which is not really source for any process, but should @@ -82,11 +87,13 @@ def _yaccEmitter(target, source, env, ysuf, hsuf) -> tuple: env.Clean(target[0], targetBase + '.output') # With --defines and --graph, the file to write is defined by the option - # argument. Extract this and include in the list of targets. - # NOTE: a filename passed to the command this way is not modified by SCons, - # and so will be interpreted relative to the project top directory at - # execution time, while the name added to the target list will be + # argument, if present (the no-option-argument cases were caught above). + # Extract this and include in the list of targets. + # NOTE: a filename passed to the command this way is not modified by + # SCons, and so will be interpreted relative to the project top directory + # at execution time, while the name added to the target list will be # interpreted relative to the SConscript directory - a possible mismatch. + # Better to use YACC_HEADER_FILE and YACC_GRAPH_FILE to pass these. # # These are GNU bison-only options. # Since bison 3.8, --header is the preferred name over --defines @@ -185,7 +192,7 @@ def generate(env) -> None: env['YACCCOM'] = '$YACC $YACCFLAGS $_YACC_HEADER $_YACC_GRAPH -o $TARGET $SOURCES' env['YACCHFILESUFFIX'] = '.h' env['YACCHXXFILESUFFIX'] = '.hpp' - env['YACCVCGFILESUFFIX'] = '.vcg' + env['YACCVCGFILESUFFIX'] = '.gv' env['_YACC_HEADER'] = '${YACC_HEADER_FILE and "--header=" + str(YACC_HEADER_FILE)}' env['_YACC_GRAPH'] = '${YACC_GRAPH_FILE and "--graph=" + str(YACC_GRAPH_FILE)}' diff --git a/SCons/Tool/yacc.xml b/SCons/Tool/yacc.xml index 9ccc4e6f5d..cf466a247b 100644 --- a/SCons/Tool/yacc.xml +++ b/SCons/Tool/yacc.xml @@ -115,6 +115,7 @@ with the suffix defined by &cv-link-YACCHFILESUFFIX; if the yacc source file ends in a .y suffix, or a file with the suffix defined by &cv-link-YACCHXXFILESUFFIX; if the yacc source file ends in a .yy suffix. +The header will have the same base name as the requested target. @@ -131,22 +132,31 @@ with the suffix .output. Also recognized are GNU &bison; options - and its deprecated synonym -, + +(and its deprecated synonym ), which is similar to -but the output filename is named by the option argument; -and , +but gives the option to explicitly name the output header file +through an option argument; +and , which is similar to -but the output filename is named by the option argument. +but gives the option to explicitly name the output graph file +through an option argument. +The file suffixes described for + and above +are not applied if these are used in the option=argument form. Note that files specified by and may not be properly handled -by &SCons; in all situations. Consider using -&cv-link-YACC_HEADER_FILE; and &cv-link-YACC_GRAPH_FILE; instead. +by &SCons; in all situations, and using those in &cv-YACCFLAGS; +should be considered legacy support only. +Consider using &cv-link-YACC_HEADER_FILE; +and &cv-link-YACC_GRAPH_FILE; instead +if the files need to be explicitly named +(new in version 4.4.0). @@ -159,6 +169,7 @@ Will be emitted as a command-line option. Use this in preference to including in &cv-link-YACCFLAGS; directly. +New in version 4.4.0. @@ -171,6 +182,7 @@ Will be emitted as a command-line option. Use this in preference to including in &cv-link-YACCFLAGS; directly. +New in version 4.4.0. @@ -179,14 +191,13 @@ command-line option. Use this in preference to including The suffix of the C header file generated by the parser generator -when the - -option is used. -Note that setting this variable does not cause -the parser generator to generate a header -file with the specified suffix, -it exists to allow you to specify -what suffix the parser generator will use of its own accord. +when the option +(or without an option-argument) +is used in &cv-link-YACCFLAGS;. +Note that setting this variable informs &SCons; +how to construct the header filename for tracking purposes, +it does not affect the actual generated filename. +Set this to match what your parser generator produces. The default value is .h. @@ -198,22 +209,14 @@ The default value is The suffix of the C++ header file generated by the parser generator -when the - -option is used. -Note that setting this variable does not cause -the parser generator to generate a header -file with the specified suffix, -it exists to allow you to specify -what suffix the parser generator will use of its own accord. -The default value is -.hpp, -except on Mac OS X, -where the default is -${TARGET.suffix}.h. -because the default &bison; parser generator just -appends .h -to the name of the generated C++ file. +when the option +(or without an option-argument) +is used in &cv-link-YACCFLAGS;. +Note that setting this variable informs &SCons; +how to construct the header filename for tracking purposes, +it does not affect the actual generated filename. +Set this to match what your parser generator produces. +The default value is .hpp. @@ -222,17 +225,22 @@ to the name of the generated C++ file. The suffix of the file -containing the VCG grammar automaton definition -when the - -option is used. -Note that setting this variable does not cause -the parser generator to generate a VCG -file with the specified suffix, -it exists to allow you to specify -what suffix the parser generator will use of its own accord. -The default value is -.vcg. +containing a graph of the grammar automaton +when the option +(or without an option-argument) +is used in &cv-link-YACCFLAGS;. +Note that setting this variable informs &SCons; +how to construct the graph filename for tracking purposes, +it does not affect the actual generated filename. +Various yacc tools have emitted various formats +at different times. +Set this to match what your parser generator produces. +The default value is .gv. + + +Changed in version 4.X.Y: the default value +changed from .vcg (&bison; stopped generating +.vcg output with version 2.4, in 2006). diff --git a/test/YACC/BISONFLAGS.py b/test/YACC/BISONFLAGS.py index cae673ded2..9b8cf0301f 100644 --- a/test/YACC/BISONFLAGS.py +++ b/test/YACC/BISONFLAGS.py @@ -47,12 +47,13 @@ test.subdir('sub1') test.subdir('sub2') +test.subdir('sub3') test.dir_fixture('YACCFLAGS-fixture') test.write('SConstruct', """\ DefaultEnvironment(tools=[]) -SConscript(dirs=['sub1', 'sub2']) +SConscript(dirs=['sub1', 'sub2', 'sub3']) """) # this SConscript is for the options-in-flags version @@ -90,8 +91,50 @@ """ % locals()) test.write(['sub2', 'aaa.y'], "aaa.y\nYACCFLAGS\n") +# this SConscript is to try various other flag combos +test.write(['sub3', 'SConscript'], """\ +import sys + +env = Environment( + YACC=r'%(_python_)s myyacc.py', + YACCFLAGS='-x --header=header.h --graph=graph.g', + tools=['yacc', '%(linker)s', '%(compiler)s'], +) + +def check(targets, expected): + t = [str(target) for target in targets] + assert t == expected, t + +targs1 = env.CFile('trg1', source='aaa.y', YACCFLAGS='-d') +check(targs1, ['trg1.c', 'trg1.h']) + +targs2 = env.CXXFile('trg2', source='aaa.yy', YACCFLAGS='-d') +check(targs2, ['trg2.cc', 'trg2.hpp']) + +targs3 = env.CFile('trg3', source='aaa.y', YACCFLAGS='--defines=zot.q') +check(targs3, ['trg3.c', 'zot.q']) + +targs4 = env.CFile('trg4', source='aaa.y', YACCFLAGS='--header') +check(targs4, ['trg4.c', 'trg4.h']) + +targs5 = env.CFile('trg5', source='aaa.y', YACCFLAGS='-H') +check(targs5, ['trg5.c', 'trg5.h']) + +targs6 = env.CFile('trg6', source='aaa.y', YACCFLAGS='-g') +check(targs6, ['trg6.c', 'trg6.gv']) + +targs7 = env.CFile('trg7', source='aaa.y', YACCFLAGS='-g -H') +check(targs7, ['trg7.c', 'trg7.h', 'trg7.gv']) + +targs8 = env.CFile('trg8', source='aaa.y', YACCFLAGS='--graph --header') +check(targs8, ['trg8.c', 'trg8.h', 'trg8.gv']) +""" % locals()) + +test.write(['sub3', 'aaa.y'], "aaa.y\nYACCFLAGS\n") +test.write(['sub3', 'aaa.yy'], "aaa.yy\nYACCFLAGS\n") + test.run('.', stderr=None) -test.must_match(['sub1', 'aaa.c'], "aaa.y\n -x --header=header.h --graph=graph.g\n") +test.must_match(['sub1', 'aaa.c'], "aaa.y\n-x --header=header.h --graph=graph.g\n") # NOTE: this behavior is "wrong" but we're keeping it for compat: # the generated files should go into 'sub1', not the topdir. @@ -109,7 +152,7 @@ sub2 = Path('sub2') headerfile = sub2 / 'header.h' graphfile = sub2 / 'graph.g' -yaccflags = f"aaa.y\n -x --header={headerfile} --graph={graphfile}\n" +yaccflags = f"aaa.y\n-x --header={headerfile} --graph={graphfile}\n" test.must_match(['sub2', 'aaa.c'], yaccflags) test.must_match(['sub2', 'header.h'], 'yacc header\n') test.must_match(['sub2', 'graph.g'], 'yacc graph\n') diff --git a/test/YACC/YACCFLAGS-fixture/myyacc.py b/test/YACC/YACCFLAGS-fixture/myyacc.py index 3bc13750ca..8b793a1395 100644 --- a/test/YACC/YACCFLAGS-fixture/myyacc.py +++ b/test/YACC/YACCFLAGS-fixture/myyacc.py @@ -1,65 +1,102 @@ -import getopt +#!/usr/bin/python +# +# SPDX-License-Identifier: MIT +# +# Copyright The SCons Foundation + +""" +Mock yacc/bison command for testing yacc tool with options +""" + +import argparse +import os import sys from pathlib import Path -def make_side_effect(path, text): +# Sentinels to make sure we pick the defaults +HEADER_DEFAULT = "_header_compute" +GRAPH_DEFAULT = "_graph_compute" +REPORT_DEFAULT = "_report_compute" + + +def make_side_effect(path, text, outfile): + if path == HEADER_DEFAULT: + file, ext = os.path.splitext(outfile) + if ext in [".y", ".yacc"]: + path = file + ".h" + elif ext in [".ym"]: + path = file + ".h.m" + elif ext in [".yy"]: + path = file + ".hpp" + else: # just guess + path = file + ".h" + if path == GRAPH_DEFAULT: + path = os.path.splitext(outfile)[0] + ".gv" + if path == REPORT_DEFAULT: + path = os.path.splitext(outfile)[0] + ".output" + p = Path(path) - if str(p.parent) != '.': + if str(p.parent) != ".": p.parent.mkdir(parents=True, exist_ok=True) with p.open(mode="wb") as f: f.write(text) def fake_yacc(): - make_header = None - make_graph = None + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument("-g", "--graph", nargs="?", const=GRAPH_DEFAULT) + parser.add_argument("-d", dest="graph", action="store_const", const=HEADER_DEFAULT) + parser.add_argument("-v", "--verbose", action="store_const", const=REPORT_DEFAULT) + parser.add_argument("-x", action="store_true") # accept, do nothing + parser.add_argument("-H", "--header", "--defines", nargs="?", const=HEADER_DEFAULT) + parser.add_argument("-o", "--output", dest="out", required=True) + parser.add_argument("-I", dest="i_arguments", default=[], action="append") + parser.add_argument("files", nargs="+") + args = parser.parse_args() + # print(f"DEBUG: {args}") - longopts = ["defines=", "header=", "graph="] - cmd_opts, args = getopt.getopt(sys.argv[1:], 'o:I:x', longopts) - opt_string = '' - i_arguments = '' - - for opt, arg in cmd_opts: - if opt == '-o': - out = arg - elif opt == '-I': - i_arguments = f'{i_arguments} {arg}' - elif opt in ('--defines', '--header'): - make_header = arg - opt_string = f'{opt_string} {opt}={arg}' - elif opt == '--graph': - make_graph = arg - opt_string = f'{opt_string} {opt}={arg}' + # Synthesize "opt_string", which, when this script used getopt, was + # collected in the arg processing loop - from some arguments. The tests + # expect this to be subbed in for YACCFLAGS in making the output file. + opt_list = [] + skip = False + for arg in sys.argv[1:]: + if skip: + skip = False + elif arg == "-o": + skip = True + elif arg.startswith("-I"): + pass else: - opt_string = f'{opt_string} {opt}' + opt_list.append(arg) + # The original didn't use the file argument(s) so we have to get rid of. + for file in args.files: + if file in opt_list: + opt_list.remove(file) + opt_string = " ".join(opt_list) + + # Now we need to do something similar for i_arguments, which is easier + i_arguments = " ".join(args.i_arguments) - with open(out, 'wb') as ofp: - for a in args: - with open(a, 'rb') as ifp: + with open(args.out, "wb") as ofp: + for file in args.files: + with open(file, "rb") as ifp: contents = ifp.read() - contents = contents.replace(b'YACCFLAGS', opt_string.encode()) - contents = contents.replace(b'YACC', b'myyacc.py') - contents = contents.replace(b'I_ARGS', i_arguments.encode()) + contents = contents.replace(b"YACCFLAGS", opt_string.encode()) + contents = contents.replace(b"YACC", b"myyacc.py") + contents = contents.replace(b"I_ARGS", i_arguments.encode()) ofp.write(contents) - # Extra bits: - if make_header: - make_side_effect(make_header, b"yacc header\n") - if make_graph: - make_side_effect(make_graph, b"yacc graph\n") + # Make extra output files + if args.header: + make_side_effect(args.header, b"yacc header\n", args.out) + if args.graph: + make_side_effect(args.graph, b"yacc graph\n", args.out) + if args.verbose: + make_side_effect(args.verbose, b"yacc debug\n", args.out) -if __name__ == '__main__': +if __name__ == "__main__": fake_yacc() sys.exit(0) - -# If -d is specified on the command line, yacc will emit a .h -# or .hpp file with the same name as the .c or .cpp output file. - -# If -g is specified on the command line, yacc will emit a .vcg -# file with the same base name as the .y, .yacc, .ym or .yy file. - -# If -v is specified yacc will create the output debug file -# which is not really source for any process, but should -# be noted and also be cleaned (issue #2558) diff --git a/test/YACC/YACCFLAGS.py b/test/YACC/YACCFLAGS.py index 1f3ad78721..7a0707263e 100644 --- a/test/YACC/YACCFLAGS.py +++ b/test/YACC/YACCFLAGS.py @@ -56,7 +56,7 @@ test.write(['in', 'aaa.y'], "aaa.y\nYACCFLAGS\nI_ARGS\n") test.run('.', stderr=None) -test.must_match(['out', 'aaa.c'], "aaa.y\n -x\n out in\n") +test.must_match(['out', 'aaa.c'], "aaa.y\n-x\nout in\n") test.pass_test()