Source code for e3_build_tools.fs.make

"""Module for functions relating to make."""

import logging
import re
import tempfile
from pathlib import Path
from typing import Iterable, List, Set, Tuple

from e3_build_tools.utils import run_make

[docs] UNDEF_REGEX = r".*warning: undefined variable [`']([^']+)'"
[docs] CXX_FLAGS_REGEX = r"^([^#]*CXXFLAGS)\s*[+:?]?=.*-std=([^\s]+)"
# Some variables could be defined in either of these files
[docs] CONFIG_FILES = ["CONFIG_MODULE", "CONFIG_OPTIONS"]
[docs] EXCEPTIONS = [ "REQUIRE_CONFIG", "E3_REQUIRE_CONFIG", "E3_REQUIRE_TOOLS", "E3_SITEMODS_PATH", "COMMON_DIR", "FETCH_BUILD_NUMBER", "DEP", "OBJ", "QUIET", "TOP", "MAKECMDGOALS", "T_A", ]
[docs] logger = logging.getLogger(__name__)
[docs] def parse_file_for_cxx_stds(f: Iterable) -> List[str]: """Return all c++ std definitions in a makefile.""" stds = [] for line in f: match = re.match(CXX_FLAGS_REGEX, line) if match: logger.debug(f"Found {match.group(1)} with -std={match.group(2)}") stds.append(match.group(2)) return stds
[docs] def find_cxx_stds(wrapper_path: Path) -> List[Tuple[Path, List[str]]]: """Return all c++ std definitions in all makefiles.""" stds = [] for fname in wrapper_path.glob("*.Makefile"): logger.debug(f"Checking file {fname} for CXX std definitions") with fname.open() as f: file_stds = parse_file_for_cxx_stds(f) if file_stds: stds.append((fname, file_stds)) for fname, file_stds in stds: logger.info(f"{fname} contains the c++ standards {file_stds}") return stds
[docs] def create_tmp_makefile(tmp_path: Path, wrapper_path: Path) -> None: """Create a temporary makefile to try to determine which variables are undefined.""" makefile_path = tmp_path / "Makefile" with open(makefile_path, "w") as f: f.write("VARS_EXCLUDE:=$(.VARIABLES)\n") # Read in the configure files from the actual module in order to catch # the *_DEP_VERSION, etc. definitions for cfile in filter( lambda x: x.is_file(), [wrapper_path / "configure" / cf for cf in CONFIG_FILES], ): f.write(f"include {cfile.absolute()}\n") # Read in the actual makefile(s) for fname in wrapper_path.glob("*.Makefile"): f.write(f"include {fname.absolute()}\n") # We need to force `make` to actually evaluate all of the variables in order to # test for them being undefined f.write( "$(foreach v,$(filter-out $(VARS_EXCLUDE),$(.VARIABLES)),$(eval $v:=$($v)))\n" ) with open(makefile_path, "r") as f: logger.debug("Generated makefile:\n" + f.read())
[docs] def find_undefined_vars(wrapper_path: Path) -> Set[str]: """Run `make` to see which variables have not been defined. Compile CONFIG_MODULE, CONFIG_OPTIONS, and all *.Makefiles into a single dummy file. Test this using the flag --warn-undefined-variables """ undefined = set() with tempfile.TemporaryDirectory() as d: tmp_dir = Path(d) create_tmp_makefile(tmp_dir, wrapper_path) result = run_make(tmp_dir, "-n", "--warn-undefined-variables") for line in result.stderr.split("\n"): match = re.match(UNDEF_REGEX, line) if match: logger.debug(match.group(0)) if match.group(1) not in EXCEPTIONS: undefined.add(match.group(1)) if undefined: logger.info("Undefined variables: " + str(undefined)) return undefined