From: Prarit Bhargava <pra...@redhat.com>

redhat/configs: Add evaluate_configs.py and documentation

Add a new Python tool for managing kernel CONFIG_* settings in the 
redhat/configs directory.
The tool helps maintainers verify and manage kernel configuration options by:

- Analyzing configuration layering and priorities defined in priority.* files
- Identifying and removing redundant CONFIG settings
- Moving common settings between rhel and fedora to the common directory
- Providing detailed analysis of CONFIG values across variants and architectures

The tool supports several operations:

- Show CONFIG values across all variants (-c/--config)
- Remove redundant CONFIGs (-f/--fix)
- Move common settings to common directory (-j/--common)
- Debug output (-d/--debug)
- List all locations of a CONFIG (-l/--list)

Also add documentation in README.evaluate_configs explaining the tool's
usage, options, and examples.

Signed-off-by: Prarit Bhargava <pra...@redhat.com>

diff --git a/redhat/configs/README.evaluate_configs 
b/redhat/configs/README.evaluate_configs
new file mode 100644
index blahblah..blahblah 100644
--- /dev/null
+++ b/redhat/configs/README.evaluate_configs
@@ -0,0 +1,100 @@
+evaluate_configs.py
+
+evaluate_configs.py is a tool for verifying and managing kernel CONFIG_* 
settings in the
+redhat/configs directory. It analyzes the configuration layering and 
priorities as defined by the
+priority.* files and the directory structure, helping maintainers ensure 
consistency and eliminate
+redundant or conflicting settings.
+
+Introduction
+
+The tool combines data from the redhat/configs files and the relevant 
priority.* file to determine
+the effective value of each kernel config option for each variant and 
architecture.
+
+Basic Usage
+
+To display information about a specific config option (e.g., 
CONFIG_HEADER_TEST) for RHEL:
+
+       ./evaluate_configs.py -p priority.rhel -c CONFIG_HEADER_TEST
+
+This produces a table showing the value of the config across all variants and 
directories, with a
+legend mapping columns to the config hierarchy.
+
+Table Legend
+
+- The legend row shows subdirectory entries, mapped to the priority.* file for 
each
+  architecture/variant.
+- The leftmost column indicates the directory and config-variant (e.g., 
rhel-s390x-zfcpdump).
+- Table values:
+  y = CONFIG is set to 'y'
+  m = CONFIG is set to 'm'
+  n = CONFIG is 'not set'
+  - = CONFIG file does not exist
+  X = CONFIG file entry is not in the priority file
+
+Options
+
+-c CONFIG
+  Evaluate specific CONFIG(s). Accepts a comma-separated list or a file. If 
omitted, all CONFIGs are
+  examined.
+-d
+  Enable debug output (shows file contents and additional details).
+-p priority.file
+  Specify the priority file (required for most operations).
+-j
+  Evaluate the common directory (compares configs between rhel and fedora to 
suggest moving settings
+  to common).
+-f
+  Evaluate and fix redundant or superseded CONFIGs (removes settings that do 
not affect the final
+  value for any variant).
+-e
+  Show only the final value for a config/variant (evaluate mode).
+
+All commands except -j require a -p argument.
+
+Evaluate CONFIGs
+
+To check all CONFIGs for redundancy and remove unnecessary settings:
+
+       ./evaluate_configs.py -p priority.rhel -f
+
+Or for a single CONFIG:
+
+       ./evaluate_configs.py -p priority.rhel -f -c CONFIG_HEADER_TEST
+
+If a redundant setting is found, the script will:
+- Output an error message with details and a table of config values.
+- Remove the redundant config file (if safe to do so).
+- Stop at the first error (exit code 1).
+
+If a config cannot be safely removed (i.e., its removal would change the final 
value for any
+variant), the script will print a detailed table and explanation.
+
+Evaluate Common Directory
+
+To compare rhel and fedora configs and suggest moving common settings to the 
common directory:
+
+       ./evaluate_configs.py -j
+
+Or for a single CONFIG:
+
+       ./evaluate_configs.py -j -c CONFIG_HEADER_TEST
+
+Examples
+
+Show the status of a config:
+
+       ./evaluate_configs.py -p priority.rhel -c CONFIG_HEADER_TEST
+
+Fix all redundant configs in Fedora:
+
+       ./evaluate_configs.py -p priority.fedora -f
+
+Fix only one config in RHEL:
+
+       ./evaluate_configs.py -p priority.rhel -c CONFIG_HEADER_TEST -f
+
+Notes
+
+- The script expects the directory structure and priority files to be 
maintained as described in the
+  main README.
+- For more details on the config layering and directory layout, see 
redhat/configs/README.
diff --git a/redhat/configs/evaluate_configs.py 
b/redhat/configs/evaluate_configs.py
new file mode 100755
index blahblah..blahblah 100755
--- /dev/null
+++ b/redhat/configs/evaluate_configs.py
@@ -0,0 +1,636 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+from pathlib import Path
+from collections import defaultdict
+from typing import List, Tuple, Optional, Set, Dict
+
+class ConfigReader:
+    def __init__(self, priority_file: str, debug: bool) -> None:
+        self.priority_file = priority_file
+        self.order: List[str] = []
+        self.variants: Dict[str, List[str]] = {}
+        self.config_values: Dict[str, Dict[str, str]] = defaultdict(dict)  # 
{config_name: {path: value}}
+        self.debug: bool = debug
+
+    def msg(self, output: str) -> None:
+        if self.debug:
+            print(output)
+
+    def read_priority_file(self) -> None:
+        """Read and parse the priority file."""
+        with open(self.priority_file, 'r') as f:
+            for line in f:
+                line = line.strip()
+                if not line or line.startswith('#'):
+                    continue
+                if line.startswith('ORDER='):
+                    self.order = line.split('=', 1)[1].split()
+                elif '=' in line and not (line.startswith('EMPTY') or 
line.startswith('ORDER')):
+                    variant, fragments = line.split('=', 1)
+                    self.variants[variant.strip()] = fragments.split(':')
+
+    def read_config_file(self, config_path: Path) -> str:
+        """Read a single config file and return its value."""
+        if not config_path.exists():
+            return '-'
+        with open(config_path, 'r') as f:
+            for line in f:
+                line = line.strip()
+                # Detect 'not set' or 'not enabled' before skipping comment 
lines
+                if line.startswith('# CONFIG_') and (' is not set' in line or 
' is not enabled' in line):
+                    return 'n'
+                # Skip empty lines and other comments
+                if not line or line.startswith('#'):
+                    continue
+                if line.startswith('CONFIG_') and line.endswith('=y'):
+                    return 'y'
+                elif line.startswith('CONFIG_') and line.endswith('=m'):
+                    return 'm'
+                return line
+        return '-'
+
+    def get_config_value_for_variant(self, config_name: str, variant: str, 
debug: bool = False, exclude_path: Optional[Path] = None, exclude_path2: 
Optional[Path] = None, priority_prefix: Optional[str] = None) -> 
Tuple[Optional[str], Optional[str], List[str]]:
+        """Get the final value of a config for a specific variant following 
priority order."""
+        if variant not in self.variants:
+            self.msg(f"[DEBUG] Variant '{variant}' not found in priority 
file.")
+            return None, None, []
+
+        final_value = None
+        final_path = None
+        checked_paths = []  # Track all paths checked
+
+        self.msg(f"ORDER: {self.order}")
+        self.msg(f"Fragments for variant '{variant}': 
{self.variants[variant]}")
+
+        # Follow the ORDER of directories
+        for directory in self.order:
+            # Skip if we're looking for a specific priority and this isn't it
+            if priority_prefix and not directory.startswith(priority_prefix):
+                continue
+
+            # For each fragment in the variant
+            for frag in self.variants[variant]:
+                # Handle both generic and debug paths
+                frag_parts = frag.split('-')
+                base_path = Path(*frag_parts)
+
+                # Check generic path
+                config_path = Path(directory) / base_path / config_name
+                if config_path == exclude_path or config_path == exclude_path2:
+                    continue
+                value = self.read_config_file(config_path)
+                checked_paths.append(str(config_path))
+                if value != '-':
+                    final_value = value
+                    final_path = str(config_path)
+
+                # Check debug path if it exists
+                debug_path = Path(directory) / base_path / 'debug' / 
config_name
+                if debug_path == exclude_path or debug_path == exclude_path2:
+                    continue
+                value = self.read_config_file(debug_path)
+                checked_paths.append(str(debug_path))
+                if value != '-':
+                    final_value = value
+                    final_path = str(debug_path)
+
+        return final_path, final_value, checked_paths
+
+    def check_variant_impact(self, config_name: str, rhel_path: Path, 
fedora_path: Path, value: str, variant: str, priority_file: str) -> 
Tuple[Optional[str], Optional[str]]:
+        """Check if moving a config to common would affect a variant."""
+        reader = ConfigReader(priority_file, self.debug)
+        reader.read_priority_file()
+        # Get current final value
+        final_path, final_value, _ = 
reader.get_config_value_for_variant(config_name, variant)
+        # Check if this path affects this variant
+        variant_fragments = reader.variants[variant]
+        path_affects_variant = False
+        for frag in variant_fragments:
+            frag_parts = frag.split('-')
+            if all(part in str(rhel_path) for part in frag_parts):
+                path_affects_variant = True
+                break
+        if not path_affects_variant:
+            return None, None
+
+        # Get the value that would be used after moving to common
+        new_value = None
+        for directory in reader.order:
+            if directory.startswith('common/'):
+                continue
+            for frag in variant_fragments:
+                frag_parts = frag.split('-')
+                base_path = Path(*frag_parts)
+                check_path = Path(directory) / base_path / config_name
+                if check_path != rhel_path and check_path != fedora_path and 
check_path.exists():
+                    value = reader.read_config_file(check_path)
+                    if value != '-':
+                        new_value = value
+                        break
+            if new_value is not None:
+                break
+        # If no other configs found, use the common value
+        if new_value is None:
+            new_value = value
+        return final_value, new_value
+
+    def analyze_redundant_config(self, config_name: str, variant: 
Optional[str] = None, priority_prefix: Optional[str] = None) -> Set[Path]:
+        """Analyze which config files are redundant without removing them."""
+        variant_list = [variant] if variant else list(self.variants.keys())
+
+        # First, get all paths and their values
+        all_paths_and_values = []
+        # Search in all directories under the current directory
+        for root, _, files in os.walk('.'):
+            if config_name in files:
+                config_path = Path(root) / config_name
+                # Only include files from the current priority
+                if priority_prefix and not 
str(config_path).startswith(priority_prefix):
+                    continue
+                value = self.read_config_file(config_path)
+                all_paths_and_values.append((config_path, value))
+
+        if not all_paths_and_values:
+            return set()
+
+        self.msg("\nAll file locations:")
+        self.msg("-" * 80)
+        for path, value in sorted(all_paths_and_values):
+            self.msg(f"{path}: {value}")
+        self.msg("-" * 80)
+
+        self.msg("\nFile analysis:")
+        self.msg("-" * 80)
+
+        safe_to_remove = set()
+        # For each path, check if it's safe to remove across all variants
+        for path, value in all_paths_and_values:
+            safe_to_remove_path = True
+            impact = []
+            self.msg(f"\nAnalyzing {path}:")
+            self.msg(f"  Current value: {value}")
+
+            # Check each variant
+            for variant in variant_list:
+                self.msg(f"  Checking variant {variant}:")
+                # Get the final value for this variant
+                final_path, final_value, _ = 
self.get_config_value_for_variant(config_name, variant, 
priority_prefix=priority_prefix)
+                self.msg(f"    Current final value: {final_value} (from 
{final_path})")
+
+                # Check if this path affects this variant by checking if it's 
in the variant's fragments
+                variant_fragments = self.variants[variant]
+                path_affects_variant = False
+                for frag in variant_fragments:
+                    frag_parts = frag.split('-')
+                    if all(part in str(path) for part in frag_parts):
+                        path_affects_variant = True
+                        break
+
+                if not path_affects_variant:
+                    self.msg("    Skipping - path doesn't affect this variant")
+                    continue
+
+                # Check if removing this path would change the final value
+                new_path, new_value, _ = 
self.get_config_value_for_variant(config_name, variant, exclude_path=path, 
priority_prefix=priority_prefix)
+                self.msg(f"    Value after removal: {new_value} (from 
{new_path})")
+
+                if new_value != final_value:
+                    safe_to_remove_path = False
+                    impact.append(f"variant {variant}: would change from 
{final_value} to {new_value}")
+                    self.msg("    KEEP - removal would change value")
+
+            if safe_to_remove_path:
+                self.msg(f"REMOVE: {path}")
+                self.msg(f"  Reason: Redundant - removing won't change final 
value for any variant (value: {value})")
+                safe_to_remove.add(path)
+            else:
+                self.msg(f"KEEP: {path}")
+                self.msg("  Reason: Removing would affect:")
+                for i in impact:
+                    self.msg(f"    - {i}")
+                self.msg(f"  Current value: {value}")
+            self.msg("-" * 80)
+
+        return safe_to_remove
+
+    def process_fix_operation(self, config_name: str, priority_files: 
List[str], args: argparse.Namespace) -> None:
+        """Process the fix operation for a config."""
+        # First analyze all files to find which ones are safe to remove
+        all_safe_to_remove = set()
+        for priority_file in priority_files:
+            self.msg(f"\nAnalyzing in {priority_file}")
+            reader = ConfigReader(priority_file, args.debug)
+            reader.read_priority_file()
+            priority_prefix = priority_file.replace('priority.', '')
+            safe_paths = reader.analyze_redundant_config(config_name, 
args.variant, priority_prefix=priority_prefix)
+            # Add paths from this priority file to the set of safe paths
+            all_safe_to_remove.update(safe_paths)
+
+        # Now remove files that are safe to remove
+        if len(all_safe_to_remove) > 0:  # Explicit check for non-empty set
+            self.msg(f"\nRemoving redundant files for {config_name}:")
+            for path in sorted(all_safe_to_remove):
+                self.msg(f"Removed: {path}")
+                path.unlink()
+        else:
+            self.msg("\nNo files to remove - all_safe_to_remove is empty")
+
+    def find_common_configs(self, config_name: str) -> List[Tuple[Path, Path, 
str]]:
+        """Find config files that exist in both rhel and fedora directories."""
+        rhel_paths = set()
+        fedora_paths = set()
+
+        # Search in all directories under the current directory
+        for root, _, files in os.walk('.'):
+            if config_name in files:
+                config_path = Path(root) / config_name
+                if str(config_path).startswith('rhel/'):
+                    rhel_paths.add(config_path)
+                elif str(config_path).startswith('fedora/'):
+                    fedora_paths.add(config_path)
+
+        # Find paths that have matching structure in both rhel and fedora
+        common_paths = []
+        for rhel_path in rhel_paths:
+            # Convert rhel path to fedora path
+            fedora_path = Path('fedora') / rhel_path.relative_to('rhel')
+            if fedora_path in fedora_paths:
+                # Check if values match
+                rhel_value = self.read_config_file(rhel_path)
+                fedora_value = self.read_config_file(fedora_path)
+                if rhel_value == fedora_value:
+                    # Check if moving to common would affect any variants
+                    safe_to_move = True
+                    impact = []
+
+                    # Check all variants
+                    for variant in self.variants:
+                        # Get current final value
+                        final_path, final_value, _ = 
self.get_config_value_for_variant(config_name, variant)
+
+                        # Check if this path affects this variant
+                        variant_fragments = self.variants[variant]
+                        path_affects_variant = False
+                        for frag in variant_fragments:
+                            frag_parts = frag.split('-')
+                            if all(part in str(rhel_path) for part in 
frag_parts):
+                                path_affects_variant = True
+                                break
+
+                        if not path_affects_variant:
+                            continue
+
+                        # Get the value that would be used after moving to 
common
+                        new_value = None
+                        for directory in self.order:
+                            if directory.startswith('common/'):
+                                continue
+                            for frag in variant_fragments:
+                                frag_parts = frag.split('-')
+                                base_path = Path(*frag_parts)
+                                check_path = Path(directory) / base_path / 
config_name
+                                if check_path != rhel_path and check_path != 
fedora_path and check_path.exists():
+                                    value = self.read_config_file(check_path)
+                                    if value != '-':
+                                        new_value = value
+                                        break
+                            if new_value is not None:
+                                break
+
+                        # If no other configs found, use the common value
+                        if new_value is None:
+                            new_value = rhel_value
+
+                        if new_value != final_value:
+                            safe_to_move = False
+                            impact.append(f"variant {variant}: would change 
from {final_value} to {new_value}")
+
+                    if safe_to_move:
+                        common_paths.append((rhel_path, fedora_path, 
rhel_value))
+                    else:
+                        self.msg(f"\nKEEP: {rhel_path} and {fedora_path}")
+                        self.msg("  Reason: Moving to common would affect:")
+                        for i in impact:
+                            self.msg(f"    - {i}")
+                        self.msg(f"  Current value: {rhel_value}")
+
+        return common_paths
+
+    def move_to_common(self, rhel_path: Path, fedora_path: Path, value: str) 
-> Path:
+        """Move a config file to the common directory."""
+        # Create common path
+        common_path = Path('common') / rhel_path.relative_to('rhel')
+
+        # Create directory if it doesn't exist
+        common_path.parent.mkdir(parents=True, exist_ok=True)
+
+        # Write the value to the common file
+        self.write_config_file(common_path, value)
+
+        # Remove the rhel and fedora files
+        rhel_path.unlink()
+        fedora_path.unlink()
+
+        return common_path
+
+    def process_common_operation(self, config_name: str, priority_files: 
List[str], args: argparse.Namespace) -> None:
+        """Process the common operation for a config."""
+        # Find common configs using all priority files
+        all_priority_files = ['priority.rhel', 'priority.fedora', 
'priority.common']
+        common_paths = []
+        processed_paths = set()  # Track which paths we've already processed
+        # First check in RHEL and Fedora
+        for priority_file in ['priority.rhel', 'priority.fedora']:
+            reader = ConfigReader(priority_file, args.debug)
+            reader.read_priority_file()
+            paths = reader.find_common_configs(config_name)
+            if paths:
+                # Only add paths we haven't processed yet
+                for rhel_path, fedora_path, value in paths:
+                    if rhel_path not in processed_paths and fedora_path not in 
processed_paths:
+                        common_paths.append((rhel_path, fedora_path, value))
+                        processed_paths.add(rhel_path)
+                        processed_paths.add(fedora_path)
+        if common_paths:
+            self.msg(f"\nMoving common configs for {config_name}:")
+            for rhel_path, fedora_path, value in common_paths:
+                self.msg("Found matching configs:")
+                self.msg(f"  RHEL:   {rhel_path} = {value}")
+                self.msg(f"  Fedora: {fedora_path} = {value}")
+                # Check if moving would affect any variants in any priority 
file
+                safe_to_move = True
+                impact = []
+                for priority_file in all_priority_files:
+                    reader = ConfigReader(priority_file, args.debug)
+                    reader.read_priority_file()
+                    # Check all variants in this priority file
+                    for variant in reader.variants:
+                        final_value, new_value = 
self.check_variant_impact(config_name, rhel_path, fedora_path, value, variant, 
priority_file)
+                        if final_value is not None and new_value != 
final_value:
+                            safe_to_move = False
+                            impact.append(f"{priority_file} variant {variant}: 
would change from {final_value} to {new_value}")
+                if safe_to_move:
+                    common_path = reader.move_to_common(rhel_path, 
fedora_path, value)
+                    self.msg(f"Moved to: {common_path}")
+                else:
+                    self.msg(f"KEEP: {rhel_path} and {fedora_path}")
+                    self.msg("  Reason: Moving to common would affect:")
+                    for i in impact:
+                        self.msg(f"    - {i}")
+                    self.msg(f"  Current value: {value}")
+        else:
+            self.msg(f"\nNo common configs found for {config_name}")
+
+    def show_config_values(self, config_name: str, variant: Optional[str] = 
None, debug: bool = False, prefix: str = '',
+                          variant_width: int = 40, value_width: int = 10, 
path_width: int = 60,
+                          variant_padding: int = 2, value_padding: int = 2) -> 
None:
+        """Show values for a specific config."""
+        if variant:
+            # Show value for specific variant
+            path, value, checked_paths = 
self.get_config_value_for_variant(config_name, variant, debug)
+            if value is not None:
+                self.msg(f"{prefix}:{variant:<{variant_width-len(prefix)-1}}{' 
' * variant_padding}{value:<{value_width}}{' ' * 
value_padding}{path:<{path_width}}")
+            else:
+                self.msg(f"{prefix}:{variant:<{variant_width-len(prefix)-1}}{' 
' * variant_padding}{'X':<{value_width}}{' ' * value_padding}")
+
+            # Print detailed table
+            if checked_paths:  # Only show table if we have paths to check
+                self.msg("\nDetailed directory check:")
+                # Calculate the maximum path length for table formatting
+                max_path_len = max(len(p) for p in checked_paths)
+
+                # self.msg table header
+                self.msg(f"{'Path':<{max_path_len}} {'Value':<10}")
+                self.msg('-' * (max_path_len + 11))
+
+                # self.msg each path and its value in table format
+                for p in checked_paths:
+                    p_value = self.read_config_file(Path(p))
+                    self.msg(f"{p:<{max_path_len}} {p_value:<10}")
+            else:
+                self.msg("\nNo directories to check for this variant")
+        else:
+            # Show values for all variants
+            for variant in sorted(self.variants.keys()):
+                path, value, _ = 
self.get_config_value_for_variant(config_name, variant, debug)
+                if value is not None:
+                    
self.msg(f"{prefix}:{variant:<{variant_width-len(prefix)-1}}{' ' * 
variant_padding}{value:<{value_width}}{' ' * 
value_padding}{path:<{path_width}}")
+                else:
+                    
self.msg(f"{prefix}:{variant:<{variant_width-len(prefix)-1}}{' ' * 
variant_padding}{'X':<{value_width}}{' ' * value_padding}")
+
+    def collect_entries(self, config_name: str, variant: Optional[str] = None) 
-> List[Dict[str, str]]:
+        """Collect all entries to calculate column widths."""
+        entries: List[Dict[str, str]] = []
+        prefix = self.priority_file.replace('priority.', '')
+        if variant:
+            path, value, _ = self.get_config_value_for_variant(config_name, 
variant)
+            if value is not None:
+                entries.append({
+                    'variant': f"{prefix}:{variant}",
+                    'value': value,
+                    'path': str(path) if path is not None else ''
+                })
+            else:
+                entries.append({
+                    'variant': f"{prefix}:{variant}",
+                    'value': 'X',
+                    'path': ''
+                })
+        else:
+            for variant in sorted(self.variants.keys()):
+                path, value, _ = 
self.get_config_value_for_variant(config_name, variant)
+                if value is not None:
+                    entries.append({
+                        'variant': f"{prefix}:{variant}",
+                        'value': value,
+                        'path': str(path) if path is not None else ''
+                    })
+                else:
+                    entries.append({
+                        'variant': f"{prefix}:{variant}",
+                        'value': 'X',
+                        'path': ''
+                    })
+        return entries
+
+    def process_show_operation(self, config_name: str, priority_files: 
List[str], args: argparse.Namespace) -> None:
+        """Process the show operation for a config."""
+        # First pass: collect all entries to calculate column widths
+        all_entries = []
+        for priority_file in priority_files:
+            reader = ConfigReader(priority_file, args.debug)
+            reader.read_priority_file()
+            prefix = priority_file.replace('priority.', '')
+            entries = reader.collect_entries(config_name, args.variant)
+            all_entries.extend(entries)
+
+        # Calculate column widths
+        variant_width = max(len(entry['variant']) for entry in all_entries)
+        value_width = max(len(entry['value']) for entry in all_entries)
+        path_width = max(len(entry['path']) for entry in all_entries)
+
+        # Add padding between columns
+        variant_padding = 2
+        value_padding = 2
+
+        # Print header
+        self.msg(f"\n{config_name}")
+        self.msg(f"{'Variant':<{variant_width}} {'Value':<{value_width}} 
{'Found in':<{path_width}}")
+        self.msg('-' * (variant_width + value_width + path_width + 
variant_padding + value_padding))
+
+        # Second pass: print all entries with consistent column widths
+        for priority_file in priority_files:
+            reader = ConfigReader(priority_file, args.debug)
+            reader.read_priority_file()
+            prefix = priority_file.replace('priority.', '')
+            reader.show_config_values(config_name, args.variant, args.debug, 
prefix,
+                                   variant_width, value_width, path_width,
+                                   variant_padding, value_padding)
+
+    def write_config_file(self, config_path: Path, value: str) -> None:
+        """Write a value to a config file."""
+        # Create directory if it doesn't exist
+        config_path.parent.mkdir(parents=True, exist_ok=True)
+
+        # Read existing content
+        existing_lines = []
+        if config_path.exists():
+            with open(config_path, 'r') as f:
+                existing_lines = f.readlines()
+
+        # Find if config already exists
+        config_name = config_path.name
+        # If config_name doesn't start with CONFIG_, add it
+        if not config_name.startswith('CONFIG_'):
+            config_name = f'CONFIG_{config_name}'
+
+        found = False
+        for i, line in enumerate(existing_lines):
+            if line.startswith(f'{config_name}=') or line.startswith(f'# 
{config_name} '):
+                if value == 'n':
+                    existing_lines[i] = f'# {config_name} is not set\n'
+                else:
+                    existing_lines[i] = f'{config_name}={value}\n'
+                found = True
+                break
+
+        # If not found, append to file
+        if not found:
+            if value == 'n':
+                existing_lines.append(f'# {config_name} is not set\n')
+            else:
+                existing_lines.append(f'{config_name}={value}\n')
+
+        # Write back to file
+        with open(config_path, 'w') as f:
+            f.writelines(existing_lines)
+
+    def get_all_configs(self) -> List[str]:
+        """Get a list of all config files in the directories."""
+        configs = set()
+        for directory in self.order:
+            for variant in self.variants.values():
+                for frag in variant:
+                    frag_parts = frag.split('-')
+                    base_path = Path(*frag_parts)
+
+                    # Check generic path
+                    config_dir = Path(directory) / base_path
+                    if config_dir.exists():
+                        for config_file in config_dir.glob('CONFIG_*'):
+                            value = self.read_config_file(config_file)
+                            if value in ['y', 'n', 'm']:
+                                configs.add(config_file.name)
+
+                    # Check debug path
+                    debug_dir = config_dir / 'debug'
+                    if debug_dir.exists():
+                        for config_file in debug_dir.glob('CONFIG_*'):
+                            value = self.read_config_file(config_file)
+                            if value in ['y', 'n', 'm']:
+                                configs.add(config_file.name)
+
+        return sorted(configs)
+
+    def find_all_config_locations(self, config_name: str) -> List[Tuple[Path, 
str]]:
+        """Find all locations of a specific config file."""
+        locations = []
+        # Search in all directories under the current directory
+        for root, _, files in os.walk('.'):
+            if config_name in files:
+                config_path = Path(root) / config_name
+                value = self.read_config_file(config_path)
+                locations.append((config_path, value))
+
+        return locations
+
+def main() -> None:
+    parser = argparse.ArgumentParser(description='Evaluate kernel configs.')
+    parser.add_argument('-c', '--config', help='Evaluate specific CONFIGs 
(file or comma-separated list)')
+    parser.add_argument('-d', '--debug', action='store_true', help='Enable 
debug output')
+    parser.add_argument('-p', '--priority', help='Path to priority file (e.g., 
priority.rhel)')
+    parser.add_argument('-j', '--common', action='store_true', help='Move 
common configs to common directory')
+    parser.add_argument('-f', '--fix', action='store_true', help='Remove 
redundant config files')
+    parser.add_argument('-v', '--variant', help='Show values for specific 
variant')
+    parser.add_argument('-l', '--list', action='store_true', help='List all 
locations of a config file')
+    args = parser.parse_args()
+
+    try:
+        # If no priority file specified, use both rhel and fedora
+        priority_files = []
+        if args.priority:
+            priority_files = [args.priority]
+        else:
+            priority_files = ['priority.rhel', 'priority.fedora']
+
+        if args.list:
+            if not args.config:
+                print("Error: -c/--config is required when using -l/--list")
+                sys.exit(1)
+
+            for priority_file in priority_files:
+                reader.msg(f"\nLocations in {priority_file}:")
+                reader.msg("-" * 80)
+                reader = ConfigReader(priority_file, args.debug)
+                reader.read_priority_file()
+                locations = reader.find_all_config_locations(args.config)
+                if locations:
+                    for path, value in sorted(locations):
+                        reader.msg(f"{path}: {value}")
+                else:
+                    reader.msg("No locations found")
+            sys.exit(0)
+
+        # Get all configs to process
+        configs_to_process = []
+        if args.config:
+            configs_to_process = [args.config]
+        else:
+            # Get all configs from the first priority file
+            reader = ConfigReader(priority_files[0], args.debug)
+            reader.read_priority_file()
+            configs_to_process = reader.get_all_configs()
+            print(f"Found {len(configs_to_process)} configs to analyze")
+
+        # Process each config
+        for config_name in configs_to_process:
+            reader.msg(f"\n{'='*80}")
+            reader.msg(f"Analyzing {config_name}")
+            reader.msg(f"{'='*80}")
+            # Process the config based on the operation
+            if args.fix:
+                reader.process_fix_operation(config_name, priority_files, args)
+            elif args.common:
+                reader.process_common_operation(config_name, priority_files, 
args)
+            else:
+                reader.process_show_operation(config_name, priority_files, 
args)
+
+    except Exception as e:
+        print(f"Error: {e}", file=sys.stderr)
+        sys.exit(1)
+
+if __name__ == '__main__':
+    main()

--
https://gitlab.com/cki-project/kernel-ark/-/merge_requests/3867

-- 
_______________________________________________
kernel mailing list -- kernel@lists.fedoraproject.org
To unsubscribe send an email to kernel-le...@lists.fedoraproject.org
Fedora Code of Conduct: 
https://docs.fedoraproject.org/en-US/project/code-of-conduct/
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedoraproject.org/archives/list/kernel@lists.fedoraproject.org
Do not reply to spam, report it: 
https://pagure.io/fedora-infrastructure/new_issue

Reply via email to