summaryrefslogtreecommitdiff
path: root/rules
diff options
context:
space:
mode:
authorAlberto Sartori <alberto.sartori@huawei.com>2023-02-21 17:01:02 +0100
committerAlberto Sartori <alberto.sartori@huawei.com>2023-03-15 14:36:44 +0100
commit5986dc7eb718c206848ffaad0ab774b1dc1836cb (patch)
tree01bc6fc1cf5b54b2185c70529735e92bbc283f9d /rules
parent838bdc8729429083f1f6e6efb4c5b80a166569d3 (diff)
downloadjustbuild-5986dc7eb718c206848ffaad0ab774b1dc1836cb.tar.gz
rules: CC/auto: add "config_file" rule to generate a c/c++ header...
...starting from a template (aka configuration file), and using the variables defined via a ["CC/auto", "config"] target. For example, to use a CMake configuration file, the targets could be defined as follows ... , "foo-header-blueprint": { "type": ["@", "rules", "CC/auto", "config_file"] , "input": ["config.hpp.in"] , "output": ["config.hpp"] , "stage": ["foo"] , "magic_string": ["cmakedefine"] , "@only": ["true"] } , "foo-header": { "type": "configure" , "target": "foo-header-blueprint" , "config": { "type": "let*" , "bindings": [ [ "defines" , [ ["var", "\"string value\""] , ["FOO_MAJOR_VERSION", "3"] , ["use_this_feature", true] ] ] ] , "body": {"type": "env", "vars": ["defines"]} } } ... The file config.hpp.in may look as follows #ifndef config_cmake #define config_cmake #cmakedefine var #cmakedefine use_this_feature #cmakedefine01 use_this_feature #cmakedefine unused #define FOO_VERSION @FOO_MAJOR_VERSION@ #define DONT_TOUCH_THIS ${FOO_MAJOR_VERSION} #endif and the generated configuration file foo/config.hpp is #ifndef config_cmake #define config_cmake #define var "string value" #define use_this_feature #define use_this_feature 1 /* #undef unused */ #define FOO_VERSION 3 #define DONT_TOUCH_THIS ${FOO_MAJOR_VERSION} #endif
Diffstat (limited to 'rules')
-rw-r--r--rules/CC/auto/RULES146
-rwxr-xr-xrules/CC/auto/config_runner.py76
2 files changed, 222 insertions, 0 deletions
diff --git a/rules/CC/auto/RULES b/rules/CC/auto/RULES
index 4f38504a..7d27ff62 100644
--- a/rules/CC/auto/RULES
+++ b/rules/CC/auto/RULES
@@ -952,4 +952,150 @@
}
}
}
+, "config_file":
+ { "doc":
+ [ "Generate a C/C++ config header from a given template"
+ , ""
+ , "Generate a C/C++ configuration header using defines specified via the"
+ , "target configuration. In the usual case, a target using this rule is"
+ , "configured by depending on it from a target that uses the built-in"
+ , "\"configure\" rule."
+ ]
+ , "field_doc":
+ { "output":
+ [ "Name of the header file to generate (incl. file name ext). Components are joined with /."
+ ]
+ , "input": ["The input configuration file, used as template."]
+ , "magic_string":
+ [ "The magic string (e.g., \"cmakedefine\") which identifies in which line"
+ , "we have to \"#define\" or \"#undef\" variables according to what is"
+ , "defined in the config field \"defines\"."
+ ]
+ , "@only": ["If set, only replace @VAR@ and not ${VAR}"]
+ }
+ , "config_doc":
+ { "defines":
+ [ "Set a define to a specific value unless its value is \"null\". Must"
+ , "contain a list of pairs. The first element of each pair is the define"
+ , "name and the second argument is the value to set. Strings must be"
+ , "properly escaped. Defines generated from this field are added last,"
+ , "so that they can refer to defines from other \"defines*\" values."
+ ]
+ }
+ , "string_fields": ["magic_string", "@only", "output"]
+ , "target_fields": ["input"]
+ , "config_vars": ["defines"]
+ , "imports":
+ { "first": "first_list_entry"
+ , "last": "last_list_entry"
+ , "stage_singleton_field": ["", "stage_singleton_field"]
+ }
+ , "implicit": {"runner": ["config_runner.py"]}
+ , "expression":
+ { "type": "let*"
+ , "bindings":
+ [ [ "runner"
+ , { "type": "let*"
+ , "bindings": [["fieldname", "runner"], ["location", "runner"]]
+ , "body":
+ {"type": "CALL_EXPRESSION", "name": "stage_singleton_field"}
+ }
+ ]
+ , [ "dict-defines"
+ , { "type": "map_union"
+ , "$1":
+ { "type": "foreach"
+ , "range": {"type": "var", "name": "defines", "default": []}
+ , "var": "pair"
+ , "body":
+ { "type": "let*"
+ , "bindings":
+ [ ["list", {"type": "var", "name": "pair"}]
+ , ["key", {"type": "CALL_EXPRESSION", "name": "first"}]
+ , ["val", {"type": "CALL_EXPRESSION", "name": "last"}]
+ ]
+ , "body":
+ { "type": "singleton_map"
+ , "key": {"type": "var", "name": "key"}
+ , "value": {"type": "var", "name": "val"}
+ }
+ }
+ }
+ }
+ ]
+ , [ "magic_string"
+ , { "type": "assert_non_empty"
+ , "msg": "A non-empty string has to be provided for magic_string"
+ , "$1":
+ {"type": "join", "$1": {"type": "FIELD", "name": "magic_string"}}
+ }
+ ]
+ , [ "@only"
+ , { "type": "if"
+ , "cond": {"type": "FIELD", "name": "@only"}
+ , "then": "true"
+ , "else": "false"
+ }
+ ]
+ , [ "param-blob"
+ , { "type": "singleton_map"
+ , "key": "param-file"
+ , "value":
+ { "type": "BLOB"
+ , "data":
+ { "type": "json_encode"
+ , "$1": {"type": "var", "name": "dict-defines"}
+ }
+ }
+ }
+ ]
+ , [ "input-blob"
+ , { "type": "let*"
+ , "bindings": [["fieldname", "input"], ["location", "input-file"]]
+ , "body":
+ {"type": "CALL_EXPRESSION", "name": "stage_singleton_field"}
+ }
+ ]
+ , [ "outfile"
+ , { "type": "ACTION"
+ , "inputs":
+ { "type": "map_union"
+ , "$1":
+ [ {"type": "var", "name": "param-blob"}
+ , {"type": "var", "name": "input-blob"}
+ , {"type": "var", "name": "runner"}
+ ]
+ }
+ , "cmd":
+ [ "./runner"
+ , "input-file"
+ , "param-file"
+ , {"type": "var", "name": "magic_string"}
+ , {"type": "var", "name": "@only"}
+ ]
+ , "outs": ["out"]
+ }
+ ]
+ , [ "outfile"
+ , { "type": "singleton_map"
+ , "key":
+ { "type": "join"
+ , "separator": "/"
+ , "$1": {"type": "FIELD", "name": "output"}
+ }
+ , "value":
+ { "type": "lookup"
+ , "key": "out"
+ , "map": {"type": "var", "name": "outfile"}
+ }
+ }
+ ]
+ ]
+ , "body":
+ { "type": "RESULT"
+ , "artifacts": {"type": "var", "name": "outfile"}
+ , "runfiles": {"type": "var", "name": "outfile"}
+ }
+ }
+ }
}
diff --git a/rules/CC/auto/config_runner.py b/rules/CC/auto/config_runner.py
new file mode 100755
index 00000000..f938c272
--- /dev/null
+++ b/rules/CC/auto/config_runner.py
@@ -0,0 +1,76 @@
+#!/usr/bin/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 re import sub, match
+
+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.
+for k, v in param.items():
+ if isinstance(v, bool):
+ param[k] = ""
+
+with open(input_file) as i:
+ with open("out", "w") as o:
+ for line in i.readlines():
+ if x := re.search(
+ r"#(.*)(" + magic_string + r" )([ \t]*)([a-zA-Z0-9_]+)", line
+ ):
+ # get the VAR
+ key = x.groups()[-1]
+ if key in param:
+ line = sub(
+ f"{x.group()[1:]}",
+ f"{x.groups()[0]}define {x.groups()[2]}{key} {param[key]}",
+ line,
+ )
+ else:
+ line = f"/* #undef {x.groups()[-1]} */\n"
+ if x := re.search(
+ r"#(.*)(" + magic_string + "01 )([ \t]*)([a-zA-Z0-9_]+)", line
+ ):
+ # get the VAR
+ key = x.groups()[-1]
+ line = sub(
+ f"{x.group()[1:]}",
+ f"{x.groups()[0]}define {x.groups()[2]}{key} "
+ + str(1 if key in param else 0),
+ line,
+ )
+ if match("#[ \t]*define", line):
+ if x := re.search(r"@([a-zA-Z0-9-_]+)@", line):
+ key = x.groups()[0]
+ line = sub(x.group(), param.get(key, ""), line)
+ if not at_only:
+ if x := re.search(r"\${([a-zA-Z0-9-_]+)}", line):
+ key = x.groups()[0]
+ line = sub(r"\${" + key + r"}", param.get(key, ""), line)
+
+ print(line, end="", file=o)