github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/bobtask/target/buildinfo.go (about)

     1  package target
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"io/fs"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  
    11  	"github.com/benchkram/bob/bobtask/buildinfo"
    12  	"github.com/benchkram/bob/pkg/file"
    13  	"github.com/benchkram/bob/pkg/filehash"
    14  	"github.com/benchkram/bob/pkg/usererror"
    15  	"github.com/benchkram/errz"
    16  )
    17  
    18  // BuildInfo reads file info and computes the target hash
    19  // for filesystem and docker targets.
    20  func (t *T) BuildInfo() (bi *buildinfo.Targets, err error) {
    21  	defer errz.Recover(&err)
    22  
    23  	bi = buildinfo.NewTargets()
    24  
    25  	// Filesystem
    26  	buildInfoFilesystem, err := t.buildinfoFiles(t.filesystemEntriesRaw)
    27  	errz.Fatal(err)
    28  	bi.Filesystem = buildInfoFilesystem
    29  
    30  	// Docker
    31  	for _, image := range t.dockerImages {
    32  		hash, err := t.dockerImageHash(image)
    33  		errz.Fatal(err)
    34  
    35  		bi.Docker[image] = buildinfo.BuildInfoDocker{Hash: hash}
    36  	}
    37  
    38  	return bi, nil
    39  }
    40  
    41  func (t *T) buildinfoFiles(paths []string) (bi buildinfo.BuildInfoFiles, _ error) {
    42  	bi = *buildinfo.NewBuildInfoFiles()
    43  
    44  	h := filehash.New()
    45  
    46  	// Use a sorted path array to assure the hash of all files
    47  	// is computed in a consistent order.
    48  	sort.Strings(paths)
    49  
    50  	for _, path := range paths {
    51  		path = filepath.Join(t.dir, path)
    52  
    53  		if !file.Exists(path) {
    54  			return buildinfo.BuildInfoFiles{}, usererror.Wrapm(ErrTargetDoesNotExist, fmt.Sprintf("[path: %q]", path))
    55  		}
    56  		targetInfo, err := os.Lstat(path)
    57  		if err != nil {
    58  			return buildinfo.BuildInfoFiles{}, fmt.Errorf("failed to get file info %q: %w", path, err)
    59  		}
    60  
    61  		if targetInfo.IsDir() {
    62  			if err := filepath.WalkDir(path, func(p string, f fs.DirEntry, err error) error {
    63  				if ShouldIgnore(p) {
    64  					return nil
    65  				}
    66  				if err != nil {
    67  					return err
    68  				}
    69  				if f.IsDir() {
    70  					// we add directories to the list of files to later verify target, but no need for size and hash
    71  					bi.Files[p] = buildinfo.BuildInfoFile{Size: -1, Hash: ""}
    72  					return nil
    73  				}
    74  
    75  				err = h.AddFile(p)
    76  				if err != nil {
    77  					return fmt.Errorf("failed to hash target %q: %w", f, err)
    78  				}
    79  
    80  				info, err := f.Info()
    81  				if err != nil {
    82  					return fmt.Errorf("failed to get file info %q: %w", p, err)
    83  				}
    84  
    85  				contentHash, err := filehash.HashOfFile(p)
    86  				if err != nil {
    87  					return fmt.Errorf("failed to get file hash %q: %w", p, err)
    88  				}
    89  
    90  				bi.Files[p] = buildinfo.BuildInfoFile{Size: info.Size(), Hash: contentHash}
    91  
    92  				return nil
    93  			}); err != nil {
    94  				return buildinfo.BuildInfoFiles{}, fmt.Errorf("failed to walk dir %q: %w", path, err)
    95  			}
    96  			// TODO: what happens on a empty dir?
    97  		} else {
    98  			if ShouldIgnore(path) {
    99  				continue
   100  			}
   101  			err = h.AddFile(path)
   102  			if err != nil {
   103  				return buildinfo.BuildInfoFiles{}, fmt.Errorf("failed to hash target %q: %w", path, err)
   104  			}
   105  			contentHash, err := filehash.HashOfFile(path)
   106  			if err != nil {
   107  				return buildinfo.BuildInfoFiles{}, fmt.Errorf("failed to get file hash %q: %w", path, err)
   108  			}
   109  			bi.Files[path] = buildinfo.BuildInfoFile{Size: targetInfo.Size(), Hash: contentHash}
   110  		}
   111  	}
   112  
   113  	bi.Hash = hex.EncodeToString(h.Sum())
   114  
   115  	return bi, nil
   116  }