go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipkg/base/generators/embed.go (about) 1 // Copyright 2023 The LUCI 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 generators 16 17 import ( 18 "context" 19 "crypto" 20 "embed" 21 "fmt" 22 "io/fs" 23 "path" 24 "path/filepath" 25 26 "go.chromium.org/luci/cipkg/base/actions" 27 "go.chromium.org/luci/cipkg/core" 28 ) 29 30 type EmbeddedFiles struct { 31 name string 32 33 ref string 34 dir string 35 efs embed.FS 36 37 modeOverride func(info fs.FileInfo) (fs.FileMode, error) 38 } 39 40 // InitEmbeddedFS registers the embed.FS to copy executor and returns the 41 // corresponding generator. 42 // It should only be used in init() or on global variables e.g. 43 // //go:embed something 44 // var somethingEmbed embed.FS 45 // var somethingGen = InitEmbeddedFS("derivationName", somethingEmbed) 46 func InitEmbeddedFS(name string, e embed.FS) *EmbeddedFiles { 47 h := crypto.SHA256.New() 48 if err := getHashFromFS(e, h); err != nil { 49 // Embedded files are frozen after build. Panic since this is more like a 50 // build failure. 51 panic(err) 52 } 53 ref := fmt.Sprintf("%x", h.Sum([]byte(name))) 54 actions.RegisterEmbed(ref, e) 55 return &EmbeddedFiles{ 56 name: name, 57 58 ref: ref, 59 dir: ".", 60 efs: e, 61 } 62 } 63 64 func (e *EmbeddedFiles) Generate(ctx context.Context, plats Platforms) (*core.Action, error) { 65 efs, err := fs.Sub(e.efs, e.dir) 66 if err != nil { 67 return nil, err 68 } 69 70 files := make(map[string]*core.ActionFilesCopy_Source) 71 if err := fs.WalkDir(efs, ".", func(name string, d fs.DirEntry, err error) error { 72 if err != nil { 73 return err 74 } 75 if d.IsDir() { 76 return nil 77 } 78 if name == "." { 79 return fmt.Errorf("root dir can't be file") 80 } 81 82 mode := fs.FileMode(0o666) 83 if e.modeOverride != nil { 84 info, err := d.Info() 85 if err != nil { 86 return err 87 } 88 if mode, err = e.modeOverride(info); err != nil { 89 return err 90 } 91 } 92 93 files[filepath.FromSlash(name)] = &core.ActionFilesCopy_Source{ 94 Content: &core.ActionFilesCopy_Source_Embed_{ 95 Embed: &core.ActionFilesCopy_Source_Embed{Ref: e.ref, Path: path.Join(e.dir, name)}, 96 }, 97 Mode: uint32(mode), 98 } 99 return nil 100 }); err != nil { 101 return nil, err 102 } 103 104 return &core.Action{ 105 Name: e.name, 106 Spec: &core.Action_Copy{ 107 Copy: &core.ActionFilesCopy{Files: files}, 108 }, 109 }, nil 110 } 111 112 // SubDir returns a generator copies files in the sub directory of the source. 113 func (e *EmbeddedFiles) SubDir(dir string) *EmbeddedFiles { 114 ret := *e 115 ret.dir = path.Join(e.dir, dir) 116 return &ret 117 } 118 119 // WithModeOverride overrides file modes while copying. 120 func (e *EmbeddedFiles) WithModeOverride(f func(info fs.FileInfo) (fs.FileMode, error)) *EmbeddedFiles { 121 ret := *e 122 ret.modeOverride = f 123 return &ret 124 }