@@ -23,9 +23,10 @@ import (
2323 "time"
2424
2525 "github.com/rogpeppe/go-internal/lockedfile"
26+ "github.com/rogpeppe/go-internal/robustio"
2627
28+ "github.com/golangci/golangci-lint/v2/internal/go/base"
2729 "github.com/golangci/golangci-lint/v2/internal/go/mmap"
28- "github.com/golangci/golangci-lint/v2/internal/go/robustio"
2930)
3031
3132// An ActionID is a cache action key, the hash of a complete description of a
@@ -41,8 +42,8 @@ type Cache interface {
4142 // Get returns the cache entry for the provided ActionID.
4243 // On miss, the error type should be of type *entryNotFoundError.
4344 //
44- // After a success call to Get, OutputFile(Entry.OutputID) must
45- // exist on disk for until Close is called (at the end of the process).
45+ // After a successful call to Get, OutputFile(Entry.OutputID) must
46+ // exist on disk until Close is called (at the end of the process).
4647 Get (ActionID ) (Entry , error )
4748
4849 // Put adds an item to the cache.
@@ -53,14 +54,14 @@ type Cache interface {
5354 // As a special case, if the ReadSeeker is of type noVerifyReadSeeker,
5455 // the verification from GODEBUG=goverifycache=1 is skipped.
5556 //
56- // After a success call to Get , OutputFile(Entry. OutputID) must
57- // exist on disk for until Close is called (at the end of the process).
57+ // After a successful call to Put , OutputFile(OutputID) must
58+ // exist on disk until Close is called (at the end of the process).
5859 Put (ActionID , io.ReadSeeker ) (_ OutputID , size int64 , _ error )
5960
6061 // Close is called at the end of the go process. Implementations can do
6162 // cache cleanup work at this phase, or wait for and report any errors from
62- // background cleanup work started earlier. Any cache trimming should in one
63- // process should not violate cause the invariants of this interface to be
63+ // background cleanup work started earlier. Any cache trimming in one
64+ // process should not cause the invariants of this interface to be
6465 // violated in another process. Namely, a cache trim from one process should
6566 // not delete an ObjectID from disk that was recently Get or Put from
6667 // another process. As a rule of thumb, don't trim things used in the last
@@ -105,7 +106,7 @@ func Open(dir string) (*DiskCache, error) {
105106 }
106107 for i := 0 ; i < 256 ; i ++ {
107108 name := filepath .Join (dir , fmt .Sprintf ("%02x" , i ))
108- if err := os .MkdirAll (name , 0744 ); err != nil {
109+ if err := os .MkdirAll (name , 0o777 ); err != nil {
109110 return nil , err
110111 }
111112 }
@@ -161,13 +162,13 @@ var errVerifyMode = errors.New("gocacheverify=1")
161162var DebugTest = false
162163
163164// func init() { initEnv() }
164-
165+ //
165166// var (
166167// gocacheverify = godebug.New("gocacheverify")
167168// gocachehash = godebug.New("gocachehash")
168169// gocachetest = godebug.New("gocachetest")
169170// )
170-
171+ //
171172// func initEnv() {
172173// if gocacheverify.Value() == "1" {
173174// gocacheverify.IncNonDefault()
@@ -258,10 +259,7 @@ func (c *DiskCache) get(id ActionID) (Entry, error) {
258259 return missing (errors .New ("negative timestamp" ))
259260 }
260261
261- err = c .used (c .fileName (id , "a" ))
262- if err != nil {
263- return Entry {}, fmt .Errorf ("failed to mark %s as used: %w" , c .fileName (id , "a" ), err )
264- }
262+ c .markUsed (c .fileName (id , "a" ))
265263
266264 return Entry {buf , size , time .Unix (0 , tm )}, nil
267265}
@@ -305,25 +303,35 @@ func GetBytes(c Cache, id ActionID) ([]byte, Entry, error) {
305303// GetMmap looks up the action ID in the cache and returns
306304// the corresponding output bytes.
307305// GetMmap should only be used for data that can be expected to fit in memory.
308- func GetMmap (c Cache , id ActionID ) ([]byte , Entry , error ) {
306+ func GetMmap (c Cache , id ActionID ) ([]byte , Entry , bool , error ) {
309307 entry , err := c .Get (id )
310308 if err != nil {
311- return nil , entry , err
309+ return nil , entry , false , err
312310 }
313- md , err := mmap .Mmap (c .OutputFile (entry .OutputID ))
311+ md , opened , err := mmap .Mmap (c .OutputFile (entry .OutputID ))
314312 if err != nil {
315- return nil , Entry {}, err
313+ return nil , Entry {}, opened , err
316314 }
317315 if int64 (len (md .Data )) != entry .Size {
318- return nil , Entry {}, & entryNotFoundError {Err : errors .New ("file incomplete" )}
316+ return nil , Entry {}, true , & entryNotFoundError {Err : errors .New ("file incomplete" )}
319317 }
320- return md .Data , entry , nil
318+ return md .Data , entry , true , nil
321319}
322320
323321// OutputFile returns the name of the cache file storing output with the given OutputID.
324322func (c * DiskCache ) OutputFile (out OutputID ) string {
325323 file := c .fileName (out , "d" )
326- c .used (file )
324+ isDir := c .markUsed (file )
325+ if isDir { // => cached executable
326+ entries , err := os .ReadDir (file )
327+ if err != nil {
328+ return fmt .Sprintf ("DO NOT USE - missing binary cache entry: %v" , err )
329+ }
330+ if len (entries ) != 1 {
331+ return "DO NOT USE - invalid binary cache entry"
332+ }
333+ return filepath .Join (file , entries [0 ].Name ())
334+ }
327335 return file
328336}
329337
@@ -345,7 +353,7 @@ const (
345353 trimLimit = 5 * 24 * time .Hour
346354)
347355
348- // used makes a best-effort attempt to update mtime on file,
356+ // markUsed makes a best-effort attempt to update mtime on file,
349357// so that mtime reflects cache access time.
350358//
351359// Because the reflection only needs to be approximate,
@@ -354,25 +362,17 @@ const (
354362// mtime is more than an hour old. This heuristic eliminates
355363// nearly all of the mtime updates that would otherwise happen,
356364// while still keeping the mtimes useful for cache trimming.
357- func (c * DiskCache ) used (file string ) error {
365+ //
366+ // markUsed reports whether the file is a directory (an executable cache entry).
367+ func (c * DiskCache ) markUsed (file string ) (isDir bool ) {
358368 info , err := os .Stat (file )
359- if err == nil && c .now ().Sub (info .ModTime ()) < mtimeInterval {
360- return nil
361- }
362-
363369 if err != nil {
364- if os .IsNotExist (err ) {
365- return & entryNotFoundError {Err : err }
366- }
367- return & entryNotFoundError {Err : fmt .Errorf ("failed to stat file %s: %w" , file , err )}
370+ return false
368371 }
369-
370- err = os .Chtimes (file , c .now (), c .now ())
371- if err != nil {
372- return fmt .Errorf ("failed to change time of file %s: %w" , file , err )
372+ if now := c .now (); now .Sub (info .ModTime ()) >= mtimeInterval {
373+ os .Chtimes (file , now , now )
373374 }
374-
375- return nil
375+ return info .IsDir ()
376376}
377377
378378func (c * DiskCache ) Close () error { return c .Trim () }
@@ -410,7 +410,7 @@ func (c *DiskCache) Trim() error {
410410 // cache will appear older than it is, and we'll trim it again next time.
411411 var b bytes.Buffer
412412 fmt .Fprintf (& b , "%d" , now .Unix ())
413- if err := lockedfile .Write (filepath .Join (c .dir , "trim.txt" ), & b , 0666 ); err != nil {
413+ if err := lockedfile .Write (filepath .Join (c .dir , "trim.txt" ), & b , 0o666 ); err != nil {
414414 return err
415415 }
416416
@@ -439,6 +439,10 @@ func (c *DiskCache) trimSubdir(subdir string, cutoff time.Time) {
439439 entry := filepath .Join (subdir , name )
440440 info , err := os .Stat (entry )
441441 if err == nil && info .ModTime ().Before (cutoff ) {
442+ if info .IsDir () { // executable cache entry
443+ os .RemoveAll (entry )
444+ continue
445+ }
442446 os .Remove (entry )
443447 }
444448 }
@@ -471,7 +475,7 @@ func (c *DiskCache) putIndexEntry(id ActionID, out OutputID, size int64, allowVe
471475
472476 // Copy file to cache directory.
473477 mode := os .O_WRONLY | os .O_CREATE
474- f , err := os .OpenFile (file , mode , 0666 )
478+ f , err := os .OpenFile (file , mode , 0o666 )
475479 if err != nil {
476480 return err
477481 }
@@ -517,7 +521,21 @@ func (c *DiskCache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error
517521 if isNoVerify {
518522 file = wrapper .ReadSeeker
519523 }
520- return c .put (id , file , ! isNoVerify )
524+ return c .put (id , "" , file , ! isNoVerify )
525+ }
526+
527+ // PutExecutable is used to store the output as the output for the action ID into a
528+ // file with the given base name, with the executable mode bit set.
529+ // It may read file twice. The content of file must not change between the two passes.
530+ func (c * DiskCache ) PutExecutable (id ActionID , name string , file io.ReadSeeker ) (OutputID , int64 , error ) {
531+ if name == "" {
532+ panic ("PutExecutable called without a name" )
533+ }
534+ wrapper , isNoVerify := file .(noVerifyReadSeeker )
535+ if isNoVerify {
536+ file = wrapper .ReadSeeker
537+ }
538+ return c .put (id , name , file , ! isNoVerify )
521539}
522540
523541// PutNoVerify is like Put but disables the verify check
@@ -528,7 +546,7 @@ func PutNoVerify(c Cache, id ActionID, file io.ReadSeeker) (OutputID, int64, err
528546 return c .Put (id , noVerifyReadSeeker {file })
529547}
530548
531- func (c * DiskCache ) put (id ActionID , file io.ReadSeeker , allowVerify bool ) (OutputID , int64 , error ) {
549+ func (c * DiskCache ) put (id ActionID , executableName string , file io.ReadSeeker , allowVerify bool ) (OutputID , int64 , error ) {
532550 // Compute output ID.
533551 h := sha256 .New ()
534552 if _ , err := file .Seek (0 , 0 ); err != nil {
@@ -542,7 +560,11 @@ func (c *DiskCache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (Outp
542560 h .Sum (out [:0 ])
543561
544562 // Copy to cached output file (if not already present).
545- if err := c .copyFile (file , out , size ); err != nil {
563+ fileMode := fs .FileMode (0o666 )
564+ if executableName != "" {
565+ fileMode = 0o777
566+ }
567+ if err := c .copyFile (file , executableName , out , size , fileMode ); err != nil {
546568 return out , size , err
547569 }
548570
@@ -558,9 +580,33 @@ func PutBytes(c Cache, id ActionID, data []byte) error {
558580
559581// copyFile copies file into the cache, expecting it to have the given
560582// output ID and size, if that file is not present already.
561- func (c * DiskCache ) copyFile (file io.ReadSeeker , out OutputID , size int64 ) error {
562- name := c .fileName (out , "d" )
583+ func (c * DiskCache ) copyFile (file io.ReadSeeker , executableName string , out OutputID , size int64 , perm os. FileMode ) error {
584+ name := c .fileName (out , "d" ) // TODO(matloob): use a different suffix for the executable cache?
563585 info , err := os .Stat (name )
586+ if executableName != "" {
587+ // This is an executable file. The file at name won't hold the output itself, but will
588+ // be a directory that holds the output, named according to executableName. Check to see
589+ // if the directory already exists, and if it does not, create it. Then reset name
590+ // to the name we want the output written to.
591+ if err != nil {
592+ if ! os .IsNotExist (err ) {
593+ return err
594+ }
595+ if err := os .Mkdir (name , 0o777 ); err != nil {
596+ return err
597+ }
598+ if info , err = os .Stat (name ); err != nil {
599+ return err
600+ }
601+ }
602+ if ! info .IsDir () {
603+ return errors .New ("internal error: invalid binary cache entry: not a directory" )
604+ }
605+
606+ // directory exists. now set name to the inner file
607+ name = filepath .Join (name , executableName )
608+ info , err = os .Stat (name )
609+ }
564610 if err == nil && info .Size () == size {
565611 // Check hash.
566612 if f , err := os .Open (name ); err == nil {
@@ -585,8 +631,14 @@ func (c *DiskCache) copyFile(file io.ReadSeeker, out OutputID, size int64) error
585631 if err == nil && info .Size () > size { // shouldn't happen but fix in case
586632 mode |= os .O_TRUNC
587633 }
588- f , err := os .OpenFile (name , mode , 0666 )
634+ f , err := os .OpenFile (name , mode , perm )
589635 if err != nil {
636+ if base .IsETXTBSY (err ) {
637+ // This file is being used by an executable. It must have
638+ // already been written by another go process and then run.
639+ // return without an error.
640+ return nil
641+ }
590642 return err
591643 }
592644 defer f .Close ()
0 commit comments