gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/constraintutil/constraintutil.go (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package constraintutil provides utilities for working with Go build
    16  // constraints.
    17  package constraintutil
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"fmt"
    23  	"go/build/constraint"
    24  	"io"
    25  	"os"
    26  	"strings"
    27  )
    28  
    29  // FromReader extracts the build constraint from the Go source or assembly file
    30  // whose contents are read by r.
    31  func FromReader(r io.Reader) (constraint.Expr, error) {
    32  	// See go/build.parseFileHeader() for the "official" logic that this is
    33  	// derived from.
    34  	const (
    35  		slashStar     = "/*"
    36  		starSlash     = "*/"
    37  		gobuildPrefix = "//go:build"
    38  	)
    39  	s := bufio.NewScanner(r)
    40  	var (
    41  		inSlashStar = false // between /* and */
    42  		haveGobuild = false
    43  		e           constraint.Expr
    44  	)
    45  Lines:
    46  	for s.Scan() {
    47  		line := bytes.TrimSpace(s.Bytes())
    48  		if !inSlashStar && constraint.IsGoBuild(string(line)) {
    49  			if haveGobuild {
    50  				return nil, fmt.Errorf("multiple go:build directives")
    51  			}
    52  			haveGobuild = true
    53  			var err error
    54  			e, err = constraint.Parse(string(line))
    55  			if err != nil {
    56  				return nil, err
    57  			}
    58  		}
    59  	ThisLine:
    60  		for len(line) > 0 {
    61  			if inSlashStar {
    62  				if i := bytes.Index(line, []byte(starSlash)); i >= 0 {
    63  					inSlashStar = false
    64  					line = bytes.TrimSpace(line[i+len(starSlash):])
    65  					continue ThisLine
    66  				}
    67  				continue Lines
    68  			}
    69  			if bytes.HasPrefix(line, []byte("//")) {
    70  				continue Lines
    71  			}
    72  			// Note that if /* appears in the line, but not at the beginning,
    73  			// then the line is still non-empty, so skipping this and
    74  			// terminating below is correct.
    75  			if bytes.HasPrefix(line, []byte(slashStar)) {
    76  				inSlashStar = true
    77  				line = bytes.TrimSpace(line[len(slashStar):])
    78  				continue ThisLine
    79  			}
    80  			// A non-empty non-comment line terminates scanning for go:build.
    81  			break Lines
    82  		}
    83  	}
    84  	return e, s.Err()
    85  }
    86  
    87  // FromString extracts the build constraint from the Go source or assembly file
    88  // containing the given data. If no build constraint applies to the file, it
    89  // returns nil.
    90  func FromString(str string) (constraint.Expr, error) {
    91  	return FromReader(strings.NewReader(str))
    92  }
    93  
    94  // FromFile extracts the build constraint from the Go source or assembly file
    95  // at the given path. If no build constraint applies to the file, it returns
    96  // nil.
    97  func FromFile(path string) (constraint.Expr, error) {
    98  	f, err := os.Open(path)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	defer f.Close()
   103  	return FromReader(f)
   104  }
   105  
   106  // Combine returns a constraint.Expr that evaluates to true iff all expressions
   107  // in es evaluate to true. If es is empty, Combine returns nil.
   108  //
   109  // Preconditions: All constraint.Exprs in es are non-nil.
   110  func Combine(es []constraint.Expr) constraint.Expr {
   111  	switch len(es) {
   112  	case 0:
   113  		return nil
   114  	case 1:
   115  		return es[0]
   116  	default:
   117  		a := &constraint.AndExpr{es[0], es[1]}
   118  		for i := 2; i < len(es); i++ {
   119  			a = &constraint.AndExpr{a, es[i]}
   120  		}
   121  		return a
   122  	}
   123  }
   124  
   125  // CombineFromFiles returns a build constraint expression that evaluates to
   126  // true iff the build constraints from all of the given Go source or assembly
   127  // files evaluate to true. If no build constraints apply to any of the given
   128  // files, it returns nil.
   129  func CombineFromFiles(paths []string) (constraint.Expr, error) {
   130  	var es []constraint.Expr
   131  	for _, path := range paths {
   132  		e, err := FromFile(path)
   133  		if err != nil {
   134  			return nil, fmt.Errorf("failed to read build constraints from %q: %v", path, err)
   135  		}
   136  		if e != nil {
   137  			es = append(es, e)
   138  		}
   139  	}
   140  	return Combine(es), nil
   141  }
   142  
   143  // Lines returns a string containing build constraint directives for the given
   144  // constraint.Expr, including two trailing newlines, as appropriate for a Go
   145  // source or assembly file. At least a go:build directive will be emitted; if
   146  // the constraint is expressible using +build directives as well, then +build
   147  // directives will also be emitted.
   148  //
   149  // If e is nil, Lines returns the empty string.
   150  func Lines(e constraint.Expr) string {
   151  	if e == nil {
   152  		return ""
   153  	}
   154  
   155  	var b strings.Builder
   156  	b.WriteString("//go:build ")
   157  	b.WriteString(e.String())
   158  	b.WriteByte('\n')
   159  
   160  	if pblines, err := constraint.PlusBuildLines(e); err == nil {
   161  		for _, line := range pblines {
   162  			b.WriteString(line)
   163  			b.WriteByte('\n')
   164  		}
   165  	}
   166  
   167  	b.WriteByte('\n')
   168  	return b.String()
   169  }