2
0
Fork 0
mirror of https://github.com/bazel-contrib/rules_foreign_cc synced 2024-12-04 08:02:31 +00:00
rules_foreign_cc/tools/build_defs/shell_script_helper.bzl
irengrig 4d73887da8
Improve outputs handling: wrapper script in shell-abstract manner (#223)
* Improve outputs handling: wrapper script in shell-abstract manner

- extract wrapper script creation into a separate function,
- simplify the script and log names, also output the wrapper script text,
- introduce more shell primitives

related to #204

* Correct review comments
2019-02-14 10:00:19 +01:00

178 lines
6.3 KiB
Python

""" Contains functions for conversion from intermediate multiplatform notation for defining
the shell script into the actual shell script for the concrete platform.
Notation:
1) export <varname>=<value>
Define the environment variable with the name <varname> and value <value>.
If the <value> contains the toolchain command call (see 3), the call is replaced with needed value.
2) $$<varname>$$
Refer the environment variable with the name <varname>,
i.e. this will become $<varname> on Linux/MacOS, and %<varname>% on Windows.
3) ##<funname>## <arg1> ... <argn>
Find the shell toolchain command Starlark method with the name <funname> for that command
in a toolchain, and call it, passing <arg1> .. <argn>.
(see ./shell_toolchain/commands.bzl, ./shell_toolchain/impl/linux_commands.bzl etc.)
The arguments are space-separated; if the argument is quoted, the spaces inside the quites are
ignored.
! Escaping of the quotes inside the quoted argument is not supported, as it was not needed for now.
(quoted arguments are used for paths and never for any arbitrary string.)
The call of a shell toolchain Starlark method is performed through
//tools/build_defs/shell_toolchain/toolchains:access.bzl; please refer there for the details.
Here what is important is that the Starlark method can also add some text (function definitions)
into a "prelude" part of the shell_context.
The resulting script is constructed from the prelude part with function definitions and
the actual translated script part.
Since function definitions can call other functions, we perform the fictive translation
of the function bodies to populate the "prelude" part of the script.
"""
load("//tools/build_defs/shell_toolchain/toolchains:access.bzl", "call_shell", "create_context")
load("//tools/build_defs/shell_toolchain/toolchains:commands.bzl", "PLATFORM_COMMANDS")
def os_name(ctx):
return call_shell(create_context(ctx), "os_name")
def create_function(ctx, name, text):
return call_shell(create_context(ctx), "define_function", name, text)
def convert_shell_script(ctx, script):
""" Converts shell script from the intermediate notation to actual schell script.
Please see the file header for the notation description.
Arguments:
ctx - rule context
script - the array of script strings, each string can be of multiple lines
Output: the string with the shell script for the current execution platform
"""
return convert_shell_script_by_context(create_context(ctx), script)
def convert_shell_script_by_context(shell_context, script):
# 0. split in lines merged fragments
new_script = []
for fragment in script:
new_script += fragment.splitlines()
script = new_script
# 1. call the functions or replace export statements
script = [do_function_call(line, shell_context) for line in script]
# 2. make sure functions calls are replaced
# (it is known there is no deep recursion, do it only once)
script = [do_function_call(line, shell_context) for line in script]
# 3. same for function bodies
processed_prelude = {}
for key in shell_context.prelude.keys():
text = shell_context.prelude[key]
lines = text.splitlines()
processed_prelude[key] = "\n".join([do_function_call(line.strip(" "), shell_context) for line in lines])
for key in processed_prelude.keys():
shell_context.prelude[key] = processed_prelude[key]
script = shell_context.prelude.values() + script
# 4. replace all variable references
script = [replace_var_ref(line, shell_context) for line in script]
result = "\n".join(script)
return result
def replace_var_ref(text, shell_context):
parts = []
current = text
# long enough
for i in range(1, 100):
(before, varname, after) = extract_wrapped(current, "$$")
if not varname:
parts.append(current)
break
parts.append(before)
parts.append(shell_context.shell.use_var(varname))
current = after
return "".join(parts)
def replace_exports(text, shell_context):
text = text.strip(" ")
(varname, separator, value) = text.partition("=")
if not separator:
fail("Wrong export declaration")
(funname, after) = get_function_name(value.strip(" "))
if funname:
value = call_shell(shell_context, funname, *split_arguments(after.strip(" ")))
return call_shell(shell_context, "export_var", varname, value)
def get_function_name(text):
(funname, separator, after) = text.partition(" ")
if funname == "export":
return (funname, after)
(before, funname_extracted, after_extracted) = extract_wrapped(funname, "##", "##")
if funname_extracted and PLATFORM_COMMANDS.get(funname_extracted):
if len(before) > 0 or len(after_extracted) > 0:
fail("Something wrong with the shell command call notation: " + text)
return (funname_extracted, after)
return (None, None)
def extract_wrapped(text, prefix, postfix = None):
postfix = postfix or prefix
(before, separator, after) = text.partition(prefix)
if not separator or not after:
return (text, None, None)
(varname, separator2, after2) = after.partition(postfix)
if not separator2:
fail("Variable or function name is not marked correctly in fragment: {}".format(text))
return (before, varname, after2)
def do_function_call(text, shell_context):
(funname, after) = get_function_name(text.strip(" "))
if not funname:
return text
if funname == "export":
return replace_exports(after, shell_context)
arguments = split_arguments(after.strip(" ")) if after else []
return call_shell(shell_context, funname, *arguments)
def split_arguments(text):
parts = []
current = text.strip(" ")
# long enough
for i in range(1, 100):
if not current:
break
# we are ignoring escaped quotes
(before, separator, after) = current.partition("\"")
if not separator:
parts += current.split(" ")
break
(quoted, separator2, after2) = after.partition("\"")
if not separator2:
fail("Incorrect quoting in fragment: {}".format(current))
before = before.strip(" ")
if before:
parts += before.split(" ")
parts.append(quoted)
current = after2
return parts