rustc will check that every reachable #[cfg] matches a list of the expected config names and values. Recent versions of rustc are also complaining about #[cfg(test)], even if it is basically a standard part of the language. So, always allow it. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
		
			
				
	
	
		
			236 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			236 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/env python3
 | 
						|
 | 
						|
"""Generate rustc arguments for meson rust builds.
 | 
						|
 | 
						|
This program generates --cfg compile flags for the configuration headers passed
 | 
						|
as arguments.
 | 
						|
 | 
						|
Copyright (c) 2024 Linaro Ltd.
 | 
						|
 | 
						|
Authors:
 | 
						|
 Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
 | 
						|
 | 
						|
This program is free software; you can redistribute it and/or modify
 | 
						|
it under the terms of the GNU General Public License as published by
 | 
						|
the Free Software Foundation; either version 2 of the License, or
 | 
						|
(at your option) any later version.
 | 
						|
 | 
						|
This program is distributed in the hope that it will be useful,
 | 
						|
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
GNU General Public License for more details.
 | 
						|
 | 
						|
You should have received a copy of the GNU General Public License
 | 
						|
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
"""
 | 
						|
 | 
						|
import argparse
 | 
						|
from dataclasses import dataclass
 | 
						|
import logging
 | 
						|
from pathlib import Path
 | 
						|
from typing import Any, Iterable, List, Mapping, Optional, Set
 | 
						|
 | 
						|
try:
 | 
						|
    import tomllib
 | 
						|
except ImportError:
 | 
						|
    import tomli as tomllib
 | 
						|
 | 
						|
STRICT_LINTS = {"unknown_lints", "warnings"}
 | 
						|
 | 
						|
 | 
						|
class CargoTOML:
 | 
						|
    tomldata: Mapping[Any, Any]
 | 
						|
    workspace_data: Mapping[Any, Any]
 | 
						|
    check_cfg: Set[str]
 | 
						|
 | 
						|
    def __init__(self, path: Optional[str], workspace: Optional[str]):
 | 
						|
        if path is not None:
 | 
						|
            with open(path, 'rb') as f:
 | 
						|
                self.tomldata = tomllib.load(f)
 | 
						|
        else:
 | 
						|
            self.tomldata = {"lints": {"workspace": True}}
 | 
						|
 | 
						|
        if workspace is not None:
 | 
						|
            with open(workspace, 'rb') as f:
 | 
						|
                self.workspace_data = tomllib.load(f)
 | 
						|
            if "workspace" not in self.workspace_data:
 | 
						|
                self.workspace_data["workspace"] = {}
 | 
						|
 | 
						|
        self.check_cfg = set(self.find_check_cfg())
 | 
						|
 | 
						|
    def find_check_cfg(self) -> Iterable[str]:
 | 
						|
        toml_lints = self.lints
 | 
						|
        rust_lints = toml_lints.get("rust", {})
 | 
						|
        cfg_lint = rust_lints.get("unexpected_cfgs", {})
 | 
						|
        return cfg_lint.get("check-cfg", [])
 | 
						|
 | 
						|
    @property
 | 
						|
    def lints(self) -> Mapping[Any, Any]:
 | 
						|
        return self.get_table("lints", True)
 | 
						|
 | 
						|
    def get_table(self, key: str, can_be_workspace: bool = False) -> Mapping[Any, Any]:
 | 
						|
        table = self.tomldata.get(key, {})
 | 
						|
        if can_be_workspace and table.get("workspace", False) is True:
 | 
						|
            table = self.workspace_data["workspace"].get(key, {})
 | 
						|
 | 
						|
        return table
 | 
						|
 | 
						|
 | 
						|
@dataclass
 | 
						|
class LintFlag:
 | 
						|
    flags: List[str]
 | 
						|
    priority: int
 | 
						|
 | 
						|
 | 
						|
def generate_lint_flags(cargo_toml: CargoTOML, strict_lints: bool) -> Iterable[str]:
 | 
						|
    """Converts Cargo.toml lints to rustc -A/-D/-F/-W flags."""
 | 
						|
 | 
						|
    toml_lints = cargo_toml.lints
 | 
						|
 | 
						|
    lint_list = []
 | 
						|
    for k, v in toml_lints.items():
 | 
						|
        prefix = "" if k == "rust" else k + "::"
 | 
						|
        for lint, data in v.items():
 | 
						|
            level = data if isinstance(data, str) else data["level"]
 | 
						|
            priority = 0 if isinstance(data, str) else data.get("priority", 0)
 | 
						|
            if level == "deny":
 | 
						|
                flag = "-D"
 | 
						|
            elif level == "allow":
 | 
						|
                flag = "-A"
 | 
						|
            elif level == "warn":
 | 
						|
                flag = "-W"
 | 
						|
            elif level == "forbid":
 | 
						|
                flag = "-F"
 | 
						|
            else:
 | 
						|
                raise Exception(f"invalid level {level} for {prefix}{lint}")
 | 
						|
 | 
						|
            # This may change if QEMU ever invokes clippy-driver or rustdoc by
 | 
						|
            # hand.  For now, check the syntax but do not add non-rustc lints to
 | 
						|
            # the command line.
 | 
						|
            if k == "rust" and not (strict_lints and lint in STRICT_LINTS):
 | 
						|
                lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority))
 | 
						|
 | 
						|
    if strict_lints:
 | 
						|
        for lint in STRICT_LINTS:
 | 
						|
            lint_list.append(LintFlag(flags=["-D", lint], priority=1000000))
 | 
						|
 | 
						|
    lint_list.sort(key=lambda x: x.priority)
 | 
						|
    for lint in lint_list:
 | 
						|
        yield from lint.flags
 | 
						|
 | 
						|
 | 
						|
def generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]:
 | 
						|
    """Converts defines from config[..].h headers to rustc --cfg flags."""
 | 
						|
 | 
						|
    with open(header, encoding="utf-8") as cfg:
 | 
						|
        config = [l.split()[1:] for l in cfg if l.startswith("#define")]
 | 
						|
 | 
						|
    cfg_list = []
 | 
						|
    for cfg in config:
 | 
						|
        name = cfg[0]
 | 
						|
        if f'cfg({name})' not in cargo_toml.check_cfg:
 | 
						|
            continue
 | 
						|
        if len(cfg) >= 2 and cfg[1] != "1":
 | 
						|
            continue
 | 
						|
        cfg_list.append("--cfg")
 | 
						|
        cfg_list.append(name)
 | 
						|
    return cfg_list
 | 
						|
 | 
						|
 | 
						|
def main() -> None:
 | 
						|
    parser = argparse.ArgumentParser()
 | 
						|
    parser.add_argument("-v", "--verbose", action="store_true")
 | 
						|
    parser.add_argument(
 | 
						|
        "--config-headers",
 | 
						|
        metavar="CONFIG_HEADER",
 | 
						|
        action="append",
 | 
						|
        dest="config_headers",
 | 
						|
        help="paths to any configuration C headers (*.h files), if any",
 | 
						|
        required=False,
 | 
						|
        default=[],
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        metavar="TOML_FILE",
 | 
						|
        action="store",
 | 
						|
        dest="cargo_toml",
 | 
						|
        help="path to Cargo.toml file",
 | 
						|
        nargs='?',
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "--workspace",
 | 
						|
        metavar="DIR",
 | 
						|
        action="store",
 | 
						|
        dest="workspace",
 | 
						|
        help="path to root of the workspace",
 | 
						|
        required=False,
 | 
						|
        default=None,
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "--features",
 | 
						|
        action="store_true",
 | 
						|
        dest="features",
 | 
						|
        help="generate --check-cfg arguments for features",
 | 
						|
        required=False,
 | 
						|
        default=None,
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "--lints",
 | 
						|
        action="store_true",
 | 
						|
        dest="lints",
 | 
						|
        help="generate arguments from [lints] table",
 | 
						|
        required=False,
 | 
						|
        default=None,
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "--rustc-version",
 | 
						|
        metavar="VERSION",
 | 
						|
        dest="rustc_version",
 | 
						|
        action="store",
 | 
						|
        help="version of rustc",
 | 
						|
        required=False,
 | 
						|
        default="1.0.0",
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "--strict-lints",
 | 
						|
        action="store_true",
 | 
						|
        dest="strict_lints",
 | 
						|
        help="apply stricter checks (for nightly Rust)",
 | 
						|
        default=False,
 | 
						|
    )
 | 
						|
    args = parser.parse_args()
 | 
						|
    if args.verbose:
 | 
						|
        logging.basicConfig(level=logging.DEBUG)
 | 
						|
    logging.debug("args: %s", args)
 | 
						|
 | 
						|
    rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2]))
 | 
						|
    if args.workspace:
 | 
						|
        workspace_cargo_toml = Path(args.workspace, "Cargo.toml").resolve()
 | 
						|
        cargo_toml = CargoTOML(args.cargo_toml, str(workspace_cargo_toml))
 | 
						|
    else:
 | 
						|
        cargo_toml = CargoTOML(args.cargo_toml, None)
 | 
						|
 | 
						|
    if args.lints:
 | 
						|
        for tok in generate_lint_flags(cargo_toml, args.strict_lints):
 | 
						|
            print(tok)
 | 
						|
 | 
						|
    if rustc_version >= (1, 80):
 | 
						|
        if args.lints:
 | 
						|
            print("--check-cfg")
 | 
						|
            print("cfg(test)")
 | 
						|
            for cfg in sorted(cargo_toml.check_cfg):
 | 
						|
                print("--check-cfg")
 | 
						|
                print(cfg)
 | 
						|
        if args.features:
 | 
						|
            for feature in cargo_toml.get_table("features"):
 | 
						|
                if feature != "default":
 | 
						|
                    print("--check-cfg")
 | 
						|
                    print(f'cfg(feature,values("{feature}"))')
 | 
						|
 | 
						|
    for header in args.config_headers:
 | 
						|
        for tok in generate_cfg_flags(header, cargo_toml):
 | 
						|
            print(tok)
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 |