github.com/devster/tarreleaser@v0.0.0-20221207180803-c608f4eb8918/pkg/pipe/archive/archive.go (about) 1 package archive 2 3 import ( 4 "compress/gzip" 5 "fmt" 6 "github.com/apex/log" 7 "github.com/campoy/unique" 8 "github.com/devster/tarreleaser/pkg/archive/targz" 9 "github.com/devster/tarreleaser/pkg/context" 10 "github.com/devster/tarreleaser/pkg/static" 11 "github.com/devster/tarreleaser/pkg/tmpl" 12 "github.com/dustin/go-humanize" 13 "github.com/mattn/go-zglob" 14 "github.com/pkg/errors" 15 "os" 16 "path/filepath" 17 "strings" 18 ) 19 20 type Pipe struct{} 21 22 func (Pipe) String() string { 23 return "archiving" 24 } 25 26 func (Pipe) Default(ctx *context.Context) error { 27 if ctx.Config.Archive.Name == "" { 28 ctx.Config.Archive.Name = "release.tar.gz" 29 } 30 31 if ctx.Config.Archive.CompressionLevel == 0 { 32 ctx.Config.Archive.CompressionLevel = gzip.DefaultCompression 33 } 34 35 if len(ctx.Config.Archive.IncludeFiles) == 0 && len(ctx.Config.Archive.ExcludeFiles) == 0 { 36 ctx.Config.Archive.ExcludeFiles = []string{ 37 ".git", 38 } 39 } 40 41 // Exclude the dist directory 42 ctx.Config.Archive.ExcludeFiles = append(ctx.Config.Archive.ExcludeFiles, ctx.Config.Dist) 43 44 if len(ctx.Config.Archive.IncludeFiles) == 0 { 45 ctx.Config.Archive.IncludeFiles = []string{ 46 "./**/*", 47 } 48 } 49 50 // Release info file defaults 51 if ctx.Config.Archive.InfoFile.Name != "" || ctx.Config.Archive.InfoFile.Content != "" { 52 if ctx.Config.Archive.InfoFile.Name == "" { 53 ctx.Config.Archive.InfoFile.Name = "release.txt" 54 } 55 56 if ctx.Config.Archive.InfoFile.Content == "" { 57 ctx.Config.Archive.InfoFile.Content = static.DefaultReleaseFileContent 58 } 59 } 60 61 return nil 62 } 63 64 func (Pipe) Run(ctx *context.Context) error { 65 ctx.Config.Archive.IncludeFiles = betterGlob(ctx.Config.Archive.IncludeFiles) 66 ctx.Config.Archive.ExcludeFiles = betterGlob(ctx.Config.Archive.ExcludeFiles) 67 68 t := tmpl.New(ctx) 69 70 archiveName, err := t.Apply(ctx.Config.Archive.Name) 71 if err != nil { 72 return err 73 } 74 archivePath := filepath.Join(ctx.Config.Dist, archiveName) 75 archiveFile, err := os.Create(archivePath) 76 if err != nil { 77 return errors.Wrapf(err, "failed to create archive file: %s", archivePath) 78 } 79 defer archiveFile.Close() 80 81 wrap, err := t.Apply(ctx.Config.Archive.WrapInDirectory) 82 if err != nil { 83 return err 84 } 85 86 log.WithFields(log.Fields{ 87 "archive": archivePath, 88 "gzip_lvl": ctx.Config.Archive.CompressionLevel, 89 "wrap": wrap, 90 }).Info("creating archive") 91 92 a, err := targz.New(archiveFile, ctx.Config.Archive.CompressionLevel) 93 if err != nil { 94 return errors.Wrap(err, "failed to create archive") 95 } 96 defer a.Close() 97 98 files, err := findFiles(ctx) 99 if err != nil { 100 return fmt.Errorf("failed to find files to archive: %s", err.Error()) 101 } 102 103 // add files to archive 104 for _, f := range files { 105 name := filepath.Join(wrap, f) 106 log.Debugf("adding file: %s", f) 107 108 if err = a.Add(name, f); err != nil { 109 return fmt.Errorf("failed to add %s to the archive: %s", f, err.Error()) 110 } 111 } 112 113 // add empty dirs to archive 114 for dir, mode := range ctx.Config.Archive.EmptyDirs { 115 dir = filepath.Join(wrap, dir) 116 // we add a trailing slash so tar.Header will know this is a directory 117 dir = fmt.Sprintf("%s/", strings.TrimSuffix(dir, "/")) 118 log.Debugf("adding empty dir with mode %v: %s", mode, dir) 119 120 if err = a.AddFromString(dir, "", mode); err != nil { 121 return fmt.Errorf("failed to add empty dir %s to the archive: %s", dir, err.Error()) 122 } 123 } 124 125 // add release info file to archive 126 if err := addReleaseInfoFile(a, ctx, wrap); err != nil { 127 return errors.Wrap(err, "failed to add release file info") 128 } 129 130 archiveInfo, err := archiveFile.Stat() 131 if err != nil { 132 return errors.Wrap(err, "unable to retrieve stat on archive file") 133 } 134 135 log.WithFields(log.Fields{ 136 "files": len(files), 137 "size": humanize.Bytes(uint64(archiveInfo.Size())), 138 }).Info("archive created with success") 139 140 ctx.Archive.Path = archivePath 141 ctx.Archive.Name = archiveName 142 143 return nil 144 } 145 146 func findFiles(ctx *context.Context) (result []string, err error) { 147 for _, glob := range ctx.Config.Archive.IncludeFiles { 148 files, err := zglob.Glob(glob) 149 if err != nil { 150 return result, fmt.Errorf("include globbing failed for pattern %s: %s", glob, err.Error()) 151 } 152 // excluding files that matches exclude glob pattern 153 for _, f := range files { 154 ok, err := isFileExcluded(ctx.Config.Archive.ExcludeFiles, f) 155 if err != nil { 156 return result, err 157 } 158 159 if !ok { 160 result = append(result, f) 161 } 162 } 163 } 164 // remove duplicates 165 unique.Slice(&result, func(i, j int) bool { 166 return strings.Compare(result[i], result[j]) < 0 167 }) 168 return 169 } 170 171 func isFileExcluded(patterns []string, file string) (bool, error) { 172 for _, pattern := range patterns { 173 ok, err := zglob.Match(pattern, file) 174 if err != nil { 175 return false, fmt.Errorf("exclude globbing failed for pattern %s: %s", pattern, err.Error()) 176 } 177 if ok { 178 return true, nil 179 } 180 } 181 182 return false, nil 183 } 184 185 // Convert existing dirs into glob pattern to have the same effect that git does with the .gitignore file 186 func betterGlob(patterns []string) (result []string) { 187 for _, glob := range patterns { 188 if info, err := os.Stat(glob); err == nil && info.IsDir() { 189 result = append(result, info.Name(), filepath.Join(info.Name(), "**/*")) 190 continue 191 } 192 193 result = append(result, glob) 194 } 195 196 return 197 } 198 199 func addReleaseInfoFile(a *targz.Archive, ctx *context.Context, wrap string) error { 200 if ctx.Config.Archive.InfoFile.Name == "" { 201 return nil 202 } 203 204 t := tmpl.New(ctx) 205 206 name, err := t.Apply(ctx.Config.Archive.InfoFile.Name) 207 if err != nil { 208 return err 209 } 210 name = filepath.Join(wrap, name) 211 212 log.WithField("file", name).Info("adding release info file") 213 214 content, err := t.Apply(ctx.Config.Archive.InfoFile.Content) 215 if err != nil { 216 return err 217 } 218 219 return a.AddFromString(name, content, 0644) 220 }