-
-
Notifications
You must be signed in to change notification settings - Fork 152
/
files.go
235 lines (195 loc) · 5.74 KB
/
files.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package files
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/goreleaser/nfpm/v2/internal/glob"
)
// Content describes the source and destination
// of one file to copy into a package.
type Content struct {
Source string `yaml:"src,omitempty" json:"src,omitempty"`
Destination string `yaml:"dst,omitempty" json:"dst,omitempty"`
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Packager string `yaml:"packager,omitempty" json:"packager,omitempty"`
FileInfo *ContentFileInfo `yaml:"file_info,omitempty" json:"file_info,omitempty"`
}
type ContentFileInfo struct {
Owner string `yaml:"owner,omitempty" json:"owner,omitempty"`
Group string `yaml:"group" json:"group"`
Mode os.FileMode `yaml:"mode,omitempty" json:"mode,omitempty"`
MTime time.Time `yaml:"mtime,omitempty" json:"mtime,omitempty"`
Size int64 `yaml:"-" json:"-"`
}
// Contents list of Content to process.
type Contents []*Content
func (c Contents) Len() int {
return len(c)
}
func (c Contents) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func (c Contents) Less(i, j int) bool {
a, b := c[i], c[j]
if a.Destination != b.Destination {
return a.Destination < b.Destination
}
if a.Type != b.Type {
return a.Type < b.Type
}
return a.Packager < b.Packager
}
func (c Contents) ContainsDestination(dst string) bool {
for _, content := range c {
if strings.TrimRight(content.Destination, "/") == strings.TrimRight(dst, "/") {
return true
}
}
return false
}
func (c *Content) WithFileInfoDefaults() *Content {
cc := &Content{
Source: c.Source,
Destination: c.Destination,
Type: c.Type,
Packager: c.Packager,
FileInfo: c.FileInfo,
}
if cc.FileInfo == nil {
cc.FileInfo = &ContentFileInfo{}
}
if cc.FileInfo.Owner == "" {
cc.FileInfo.Owner = "root"
}
if cc.FileInfo.Group == "" {
cc.FileInfo.Group = "root"
}
if cc.Type == "dir" && cc.FileInfo.Mode == 0 {
cc.FileInfo.Mode = 0o755
}
// determine if we still need info
fileInfoAlreadyComplete := (!cc.FileInfo.MTime.IsZero() &&
cc.FileInfo.Mode != 0 &&
(cc.FileInfo.Size != 0 || cc.Type == "dir"))
// only stat source when we actually need more information
if cc.Source != "" && !fileInfoAlreadyComplete {
info, err := os.Stat(cc.Source)
if err == nil {
if cc.FileInfo.MTime.IsZero() {
cc.FileInfo.MTime = info.ModTime()
}
if cc.FileInfo.Mode == 0 {
cc.FileInfo.Mode = info.Mode()
}
cc.FileInfo.Size = info.Size()
}
}
if cc.FileInfo.MTime.IsZero() {
cc.FileInfo.MTime = time.Now().UTC()
}
return cc
}
// Name to part of the os.FileInfo interface
func (c *Content) Name() string {
return c.Source
}
// Size to part of the os.FileInfo interface
func (c *Content) Size() int64 {
return c.FileInfo.Size
}
// Mode to part of the os.FileInfo interface
func (c *Content) Mode() os.FileMode {
return c.FileInfo.Mode
}
// ModTime to part of the os.FileInfo interface
func (c *Content) ModTime() time.Time {
return c.FileInfo.MTime
}
// IsDir to part of the os.FileInfo interface
func (c *Content) IsDir() bool {
return false
}
// Sys to part of the os.FileInfo interface
func (c *Content) Sys() interface{} {
return nil
}
// ExpandContentGlobs gathers all of the real files to be copied into the package.
func ExpandContentGlobs(contents Contents, disableGlobbing bool) (files Contents, err error) {
for _, f := range contents {
var globbed map[string]string
switch f.Type {
case "ghost", "symlink", "dir":
// Ghost, symlinks and dirs need to be in the list, but dont glob
// them because they do not really exist
files = append(files, f.WithFileInfoDefaults())
default:
globbed, err = glob.Glob(f.Source, f.Destination, disableGlobbing)
if err != nil {
return nil, err
}
files, err = appendGlobbedFiles(files, globbed, f)
if err != nil {
return nil, err
}
}
}
err = checkNoCollisions(files)
if err != nil {
return nil, err
}
// sort the files for reproducibility and general cleanliness
sort.Sort(files)
return files, nil
}
func appendGlobbedFiles(all Contents, globbed map[string]string, origFile *Content) (Contents, error) {
for src, dst := range globbed {
// if the file has a FileInfo, we need to copy it but recalculate its size
newFileInfo := origFile.FileInfo
if newFileInfo != nil {
newFileInfoVal := *newFileInfo
newFileInfoVal.Size = 0
newFileInfo = &newFileInfoVal
}
newFile := (&Content{
Destination: ToNixPath(dst),
Source: ToNixPath(src),
Type: origFile.Type,
FileInfo: newFileInfo,
Packager: origFile.Packager,
}).WithFileInfoDefaults()
if dst, err := os.Readlink(src); err == nil {
newFile.Source = dst
newFile.Type = "symlink"
}
all = append(all, newFile)
}
return all, nil
}
var ErrContentCollision = fmt.Errorf("content collision")
func checkNoCollisions(contents Contents) error {
alreadyPresent := map[string]*Content{}
for _, elem := range contents {
present, ok := alreadyPresent[elem.Destination]
if ok && (present.Packager == "" || elem.Packager == "" || present.Packager == elem.Packager) {
if elem.Type == "dir" {
return fmt.Errorf("cannot add directory %q because it is already present: %w",
elem.Destination, ErrContentCollision)
}
return fmt.Errorf(
"cannot add %q because %q is already present at the same destination (%s): %w",
elem.Source, present.Source, present.Destination, ErrContentCollision)
}
alreadyPresent[elem.Destination] = elem
}
return nil
}
// ToNixPath converts the given path to a nix-style path.
//
// Windows-style path separators are considered escape
// characters by some libraries, which can cause issues.
func ToNixPath(path string) string {
return filepath.ToSlash(filepath.Clean(path))
}