#!/usr/bin/env python3 # Copyright 2022 Huawei Cloud Computing Technology Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json import re from sys import argv from typing import Union def get_tokens(line: str, magic_string: str) -> tuple[Union[re.Match[str], None], bool]: """Tokenize lines (strings) like the following #cmakedefine FOO bar #cmakedefine FOO @FOO@ #cmakedefine FOO ${FOO} #cmakedefine01 FOO where "cmakedefine" is the magic_string. Let us name "FOO" as the token_key, and the corresponding value (i.e., bar, @FOO@, ${FOO}, the empty string) as the token_value. The function handles any combination of spaces around the magic_string, the token_key and the token_value. """ x = re.search( r"#(.*)(" + magic_string + r"[01]*" + r")([\s]*)([a-zA-Z0-9_]+)([\s]*)(.*)", line, ) if x: return x, x.groups()[1] == f"{magic_string}01" return None, False def handle01(line: str, tokens: re.Match[str], defined: bool) -> str: groups = tokens.groups() return re.sub( tokens.group()[1:], groups[0] # spaces + "define" + groups[2] # spaces + groups[3] # token_key + " " + str(1 if defined else 0), line, ) def undefine(tokens: re.Match[str]) -> str: groups = tokens.groups() return "/* #" + groups[0] + "undef" + groups[2] + groups[3] + " */" def replace_value(tokens: re.Match[str], key: str, value: str) -> str: groups = tokens.groups() return f"#{groups[0]}define{groups[2]}{key}{groups[4]}{value}" def compute_value(token_value: str, at_only: bool, param: dict[str, str]): # example of possible token_values # - foo (a simple string) # - @FOO@ # - ${FOO} # - any combination of the above def replace_pattern_in_string( pattern: str, line: str, param: dict[str, str] ) -> str: def get_value_for_match(match: re.Match[str]) -> str: key = match.group(1) return param.get(key, "") return re.sub(pattern, get_value_for_match, line) token_value = replace_pattern_in_string(r"@([A-Za-z0-9_]+)@", token_value, param) if at_only: return token_value return replace_pattern_in_string(r"\${([A-Za-z0-9_]+)}", token_value, param) if __name__ == "__main__": input_file = argv[1] param_file = argv[2] magic_string = argv[3] at_only = argv[4] == "true" with open(param_file) as f: param = json.loads(f.read()) # In many cases, CMake simply defines some variables (without any associated # value). We handle this situation by assigning to the boolean True the empty # string. Note that no False value should be found, because the right way to set # a variable to False in the TARGETS file is to *do not mention* that variable # at all. # If a value is deliberately set to null, we will drop that key drop_keys: list[str] = [] for k, v in param.items(): if isinstance(v, bool): param[k] = "" if v == None: drop_keys.append(k) for k in drop_keys: del param[k] with open(input_file) as i: with open("out", "w") as o: for line in i.readlines(): # drop trailing '\n' line = line[:-1] tokens, is_01 = get_tokens(line, magic_string) # no magic string if not tokens: # it can be a simple comment, or a line without the magic string but with the @KEY@ or ${KEY} pattern line = compute_value(line, at_only, param) print(line, file=o) continue # line contains magic_string groups = tokens.groups() token_key: str = groups[3] if is_01: line = handle01(line, tokens, token_key in param) print(line, file=o) continue if token_key not in param: line = undefine(tokens) print(line, file=o) continue # we are in one of this situations # cmakedefine FOO # cmakedefine FOO "foo" # cmakedefine FOO @FOO@${FOO}foo # i.e., the token_value can be any combination of keys (defined # as @key@ or ${key}) and strings # therefore, we need to further tokenize the token_value # it is convenient to first tokenize token_value: str = groups[5] value = compute_value(token_value, at_only, param) line = replace_value(tokens, token_key, value) print(line, file=o)