github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/crosbundle/software.go (about)

     1  // Copyright 2021 The ChromiumOS Authors
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  package crosbundle
     6  
     7  import (
     8  	"bufio"
     9  	"context"
    10  	"fmt"
    11  	"os"
    12  	"sort"
    13  	"strings"
    14  
    15  	"go.chromium.org/tast/core/autocaps"
    16  	"go.chromium.org/tast/core/errors"
    17  	"go.chromium.org/tast/core/internal/expr"
    18  	"go.chromium.org/tast/core/internal/logging"
    19  	"go.chromium.org/tast/core/lsbrelease"
    20  
    21  	protocol "go.chromium.org/tast/core/framework/protocol"
    22  )
    23  
    24  const (
    25  	// autotestCapPrefix is the prefix for autotest-capability feature names.
    26  	autotestCapPrefix = "autotest-capability:"
    27  
    28  	// useFlagsFile is the path to the file containing USE flags enabled for
    29  	// the board.
    30  	// The tast-use-flags package attempts to install this file to /etc,
    31  	// but it gets diverted to /usr/local since it's installed for test images.
    32  	useFlagsFile = "/usr/local/etc/tast_use_flags.txt"
    33  )
    34  
    35  // detectSoftwareFeatures implements the main function of RunnerGetDUTInfoMode (i.e., except input/output
    36  // conversion for RPC).
    37  func detectSoftwareFeatures(ctx context.Context, extraUSEFlags []string) (*protocol.SoftwareFeatures, error) {
    38  	// If the file listing USE flags doesn't exist, we're probably running on a non-test
    39  	// image. Return an empty response to signal that to the caller.
    40  	if _, err := os.Stat(useFlagsFile); os.IsNotExist(err) {
    41  		return nil, nil
    42  	}
    43  
    44  	flags, err := readUSEFlagsFile(useFlagsFile)
    45  	if err != nil {
    46  		return nil, errors.Wrapf(err, "failed to read %v", useFlagsFile)
    47  	}
    48  	flags = append(flags, extraUSEFlags...)
    49  
    50  	if lr, err := lsbrelease.LoadFrom(lsbrelease.Path); err != nil {
    51  		logging.Infof(ctx, "Failed to read lsbrelease; board names in software feature definitions will not work: %v", err)
    52  	} else if board, ok := lr[lsbrelease.Board]; !ok {
    53  		logging.Infof(ctx, "Failed to find boardname in lsbrelease; board names in software feature definitions will not work")
    54  	} else {
    55  		flags = append(flags, "board:"+board)
    56  	}
    57  
    58  	autotestCaps, err := autocaps.Read(autocaps.DefaultCapabilityDir, nil)
    59  	if err != nil {
    60  		logging.Infof(ctx, "%s: %v", autocaps.DefaultCapabilityDir, err)
    61  
    62  	}
    63  
    64  	features, err := determineSoftwareFeatures(softwareFeatureDefs, flags, autotestCaps)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	return features, nil
    69  }
    70  
    71  // readUSEFlagsFile reads a list of USE flags from fn (see StaticConfig.USEFlagsFile).
    72  // Each flag should be specified on its own line, and lines beginning with '#' are ignored.
    73  func readUSEFlagsFile(fn string) ([]string, error) {
    74  	f, err := os.Open(fn)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	defer f.Close()
    79  
    80  	var flags []string
    81  	sc := bufio.NewScanner(f)
    82  	for sc.Scan() {
    83  		flag := strings.TrimSpace(sc.Text())
    84  		if flag != "" && flag[0] != '#' {
    85  			flags = append(flags, flag)
    86  		}
    87  	}
    88  	if err = sc.Err(); err != nil {
    89  		return nil, err
    90  	}
    91  	return flags, nil
    92  }
    93  
    94  // determineSoftwareFeatures computes the DUT's available and unavailable software features.
    95  // definitions maps feature names to definitions (see StaticConfig.SoftwareFeatureDefinitions).
    96  // useFlags contains a list of relevant USE flags that were set when building the system image (see StaticConfig.USEFlagsFile).
    97  // autotestCaps contains a mapping from autotest-capability names to the corresponding states.
    98  func determineSoftwareFeatures(definitions map[string]string, useFlags []string, autotestCaps map[string]autocaps.State) (
    99  	*protocol.SoftwareFeatures, error) {
   100  	var available, unavailable []string
   101  	for ft, es := range definitions {
   102  		if strings.HasPrefix(ft, autotestCapPrefix) {
   103  			return nil, fmt.Errorf("feature %q has reserved prefix %q", ft, autotestCapPrefix)
   104  		}
   105  
   106  		ex, err := expr.New(es)
   107  		if err != nil {
   108  			return nil, fmt.Errorf("failed to parse %q feature expression %q: %v", ft, es, err)
   109  		}
   110  		if ex.Matches(useFlags) {
   111  			available = append(available, ft)
   112  		} else {
   113  			unavailable = append(unavailable, ft)
   114  		}
   115  	}
   116  
   117  	for name, state := range autotestCaps {
   118  		if state == autocaps.Yes {
   119  			available = append(available, autotestCapPrefix+name)
   120  		} else {
   121  			unavailable = append(unavailable, autotestCapPrefix+name)
   122  		}
   123  	}
   124  
   125  	sort.Strings(available)
   126  	sort.Strings(unavailable)
   127  	return &protocol.SoftwareFeatures{Available: available, Unavailable: unavailable}, nil
   128  }