1515import re
1616import subprocess
1717import sys
18+ import shutil
1819from pathlib import Path
1920
2021JUST = os .environ .get ("JUST_EXECUTABLE" , "just" )
2324CMD_END = os .environ .get ("CMD_END" , "" )
2425SEMMLE_CODE = os .environ .get ("SEMMLE_CODE" )
2526
26- ENV_RE = re .compile (r"^[A-Z_][A-Z_0-9]*=.* $" )
27+ ENV_RE = re .compile (r"( ^[A-Z_][A-Z_0-9]*)=(.*) $" )
2728
2829
2930def invoke (invocation , * , cwd = None , log_prefix = "" ):
3031 prefix = f"{ log_prefix } " if log_prefix else ""
31- print (f"{ CMD_BEGIN } { prefix } { ' ' .join (invocation )} { CMD_END } " )
32+ print (f"{ CMD_BEGIN } { prefix } { ' ' .join (map ( str , invocation ) )} { CMD_END } " )
3233 try :
3334 subprocess .run (invocation , check = True , cwd = cwd )
3435 except subprocess .CalledProcessError as e :
@@ -61,11 +62,11 @@ def build_parser():
6162 parser .add_argument ("--codeql" , default = "build" if SEMMLE_CODE else "host" )
6263 parser .add_argument ("--extra-check" , action = "append" , dest = "extra_checks" )
6364 parser .add_argument ("--all-checks" , "+" , action = "store_true" , dest = "all" )
64- parser .set_defaults (extra_checks = [], tests = [], flags = [], env = [] )
65+ parser .set_defaults (extra_checks = [], tests = [], flags = [], env = {} )
6566 return parser
6667
6768
68- def parse_arguments (argv ):
69+ def parse_arguments ():
6970 """Sort a command line into the kinds that are handled differently.
7071
7172 `argparse` owns the options this script acts on itself. Everything else belongs to
@@ -74,33 +75,44 @@ def parse_arguments(argv):
7475 positionals, told apart only by how they look.
7576 """
7677 # An empty argument can come from a caller interpolating an unset variable.
77- args , rest = build_parser ().parse_known_args ([ arg for arg in argv if arg ] )
78+ args , rest = build_parser ().parse_known_args ()
7879 for arg in rest :
79- if arg .startswith ("-" ):
80+ if not arg :
81+ pass
82+ elif arg .startswith ("-" ):
8083 args .flags .append (arg )
81- elif ENV_RE .match (arg ):
82- args .env .append (arg )
84+ elif m := ENV_RE .match (arg ):
85+ k , v = m .groups ()
86+ args .env [k ] = v
8387 else :
8488 args .tests .append (arg )
8589 return args
8690
8791
88- def env_value (args , name , default ):
89- """Resolve a setting from test arguments, then the environment, then a default."""
90- for assignment in reversed (args .env ):
91- key , _ , value = assignment .partition ("=" )
92- if key == name and value :
93- return value
94- return os .environ .get (name ) or default
95-
92+ def resolve_codeql (args : argparse .Namespace ) -> Path :
93+ suffix = ".exe" if sys .platform == "win32" else ""
94+ match args .codeql :
95+ case "built" | "build" :
96+ return Path (
97+ SEMMLE_CODE , "target" , "intree" , f"codeql-{ args .language } " , "codeql"
98+ ).with_suffix (suffix )
99+ case "host" :
100+ return Path (shutil .which ("codeql" + suffix ))
101+ case _:
102+ codeql = Path (args .codeql )
103+ if codeql .is_dir ():
104+ codeql /= "codeql"
105+ return codeql .with_suffix (suffix )
106+
96107
97108def main ():
98- argv = sys . argv [ 1 :]
99- args = parse_arguments ( argv )
109+ args = parse_arguments ()
110+
100111 if args .all :
101112 # Apply what the language offered by parsing it alongside everything else, so an
102113 # offered check lands exactly where the same flag typed by hand would.
103- args = parse_arguments (argv + args .extra_checks )
114+ sys .argv [1 :1 ] = args .extra_checks
115+ args = parse_arguments ()
104116
105117 if not SEMMLE_CODE and args .codeql in ("build" , "built" ):
106118 error (
@@ -112,52 +124,33 @@ def main():
112124 if not args .tests :
113125 args .tests .append ("." )
114126
127+ os .environ .update (args .env )
128+
115129 # Resolve these only once all arguments are known, so that a `RAM_PER_THREAD=` test
116130 # argument can lower the default on memory-heavy suites.
117131 default_ram = 3000 if sys .platform == "linux" else 2048
118- ram_per_thread = int (env_value ( args , "RAM_PER_THREAD" , default_ram ))
119- cpus = int (env_value ( args , "CPUS" , os .cpu_count () or 1 ))
132+ ram_per_thread = int (os . environ . get ( "RAM_PER_THREAD" , default_ram ))
133+ cpus = int (os . environ . get ( "CPUS" , os .cpu_count () or 1 ))
120134 args .flags [:0 ] = [f"--ram={ ram_per_thread * cpus } " , f"-j{ cpus } " ]
121135
122136 if args .codeql == "build" :
123- if invoke ([JUST , args .language , "build" ], cwd = SEMMLE_CODE ) != 0 :
124- return 1
137+ if ret := invoke ([JUST , args .language , "build" ], cwd = SEMMLE_CODE ):
138+ return ret
125139
126140 if args .codeql != "host" :
127141 # Disable the default implicit config file, but keep an explicit one.
128142 # Same behavior wrt --codeql as the integration test runner.
129143 os .environ .setdefault ("CODEQL_CONFIG_FILE" , "." )
130144
131- for env_var in args .env :
132- key , _ , value = env_var .partition ("=" )
133- os .environ [key ] = value
134-
135- # Resolve codeql executable
136- if args .codeql in ("built" , "build" ):
137- codeql = Path (
138- SEMMLE_CODE , "target" , "intree" , f"codeql-{ args .language } " , "codeql"
139- )
140- elif args .codeql == "host" :
141- codeql = Path ("codeql" )
142- else :
143- codeql = Path (args .codeql )
144-
145- if codeql .is_dir ():
146- codeql = codeql / "codeql"
147-
148- # On Windows, prefer codeql.exe over the Unix shell wrapper
149- if sys .platform == "win32" and codeql .suffix != ".exe" :
150- exe = codeql .with_suffix (".exe" )
151- if exe .exists ():
152- codeql = exe
145+ codeql = resolve_codeql (args )
153146
154- if args . codeql != "host" and not codeql .exists ():
147+ if not codeql .exists ():
155148 error (f"CodeQL executable not found: { codeql } " )
156149 return 1
157150
158151 return invoke (
159- [str ( codeql ) , "test" , "run" , * args .flags , "--" , * args .tests ],
160- log_prefix = " " .join (args .env ),
152+ [codeql , "test" , "run" , * args .flags , "--" , * args .tests ],
153+ log_prefix = " " .join (f" { k } = { v } " for k , v in args .env . items () ),
161154 )
162155
163156
0 commit comments