[libc++] Make the %run substitution closer to how .pass.cpp tests are executed

Before this patch, the %run substitution did not contain the same
environment variables as normal `pass.cpp` tests. It also didn't
have the right working directory and the script wasn't aware of
potential file dependencies.

With this change, the combination of %build and %run in a .sh.cpp script
should match how pass.cpp tests are actually executed much more closely.

Cr-Mirrored-From: https://chromium.googlesource.com/external/github.com/llvm/llvm-project
Cr-Mirrored-Commit: e22fe98d059c37a605b85641f4ff8e285164ac3b
diff --git a/utils/run.py b/utils/run.py
index fcfee96..33d0095 100644
--- a/utils/run.py
+++ b/utils/run.py
@@ -14,25 +14,41 @@
 
 import subprocess
 import sys
+import argparse
 
 
 def main():
-    codesign_ident = sys.argv[1]
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--codesign_identity', type=str, required=False)
+    parser.add_argument('--working_directory', type=str, required=True)
+    parser.add_argument('--dependencies', type=str, nargs='*', required=True)
+    parser.add_argument('--env', type=str, nargs='*', required=True)
+    (args, remaining) = parser.parse_known_args(sys.argv[1:])
 
-    # Ignore 'run.py' and the codesigning identity.
-    argv = sys.argv[2:]
-
-    exec_path = argv[0]
+    if len(remaining) < 2:
+        sys.stderr.write('Missing actual commands to run')
+        exit(1)
+    remaining = remaining[1:] # Skip the '--'
 
     # Do any necessary codesigning.
-    if codesign_ident:
-        sign_cmd = ['xcrun', 'codesign', '-f', '-s', codesign_ident, exec_path]
-        cs_rc = subprocess.call(sign_cmd, env={})
-        if cs_rc != 0:
-            sys.stderr.write('Failed to codesign: ' + exec_path)
-            return cs_rc
+    if args.codesign_identity:
+        exe = remaining[0]
+        rc = subprocess.call(['xcrun', 'codesign', '-f', '-s', args.codesign_identity, exe], env={})
+        if rc != 0:
+            sys.stderr.write('Failed to codesign: ' + exe)
+            return rc
 
-    return subprocess.call(argv)
+    # Extract environment variables into a dictionary
+    env = {k : v  for (k, v) in map(lambda s: s.split('='), args.env)}
+
+    # Ensure the file dependencies exist
+    for file in args.dependencies:
+        if not os.path.exists(file):
+            sys.stderr.write('Missing file {} marked as a dependency of a test'.format(file))
+            exit(1)
+
+    # Run the executable with the given environment in the given working directory
+    return subprocess.call(remaining, cwd=args.working_directory, env=env)
 
 if __name__ == '__main__':
     exit(main())