Skip to content

Commit 6e243ce

Browse files
committed
cmd/go: have go mod vendor copy embedded files in subdirs
If a package vendored with go mod vendor depends on embedded files contained in subdirectories, copy them into the the corresponding place in the module's vendor tree. (Embeds in parent directories are disallowed by the embed pattern rules, and embeds in the same directory are copied because go mod vendor already copies the non-go files in the package's own directory). Export the vendor pattern expansion code in internal/load so internal/modcmd's vendor code can use it. Fixes #43077 Change-Id: I61edb344d73df590574a6498ffb6069e8d72a147 Reviewed-on: https://go-review.googlesource.com/c/go/+/283641 Trust: Michael Matloob <[email protected]> Trust: Bryan C. Mills <[email protected]> Run-TryBot: Michael Matloob <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent be28e5a commit 6e243ce

File tree

6 files changed

+292
-21
lines changed

6 files changed

+292
-21
lines changed

src/cmd/go/internal/list/list.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,8 +581,6 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
581581
// Show vendor-expanded paths in listing
582582
p.TestImports = p.Resolve(p.TestImports)
583583
p.XTestImports = p.Resolve(p.XTestImports)
584-
p.TestEmbedFiles = p.ResolveEmbed(p.TestEmbedPatterns)
585-
p.XTestEmbedFiles = p.ResolveEmbed(p.XTestEmbedPatterns)
586584
p.DepOnly = !cmdline[p]
587585

588586
if *listCompiled {

src/cmd/go/internal/load/pkg.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,7 +1807,7 @@ func (p *Package) load(ctx context.Context, path string, stk *ImportStack, impor
18071807
stk.Push(path)
18081808
defer stk.Pop()
18091809

1810-
p.EmbedFiles, p.Internal.Embed, err = p.resolveEmbed(p.EmbedPatterns)
1810+
p.EmbedFiles, p.Internal.Embed, err = resolveEmbed(p.Dir, p.EmbedPatterns)
18111811
if err != nil {
18121812
setError(err)
18131813
embedErr := err.(*EmbedError)
@@ -1932,17 +1932,20 @@ func (e *EmbedError) Unwrap() error {
19321932
}
19331933

19341934
// ResolveEmbed resolves //go:embed patterns and returns only the file list.
1935-
// For use by go list to compute p.TestEmbedFiles and p.XTestEmbedFiles.
1936-
func (p *Package) ResolveEmbed(patterns []string) []string {
1937-
files, _, _ := p.resolveEmbed(patterns)
1938-
return files
1935+
// For use by go mod vendor to find embedded files it should copy into the
1936+
// vendor directory.
1937+
// TODO(#42504): Once go mod vendor uses load.PackagesAndErrors, just
1938+
// call (*Package).ResolveEmbed
1939+
func ResolveEmbed(dir string, patterns []string) ([]string, error) {
1940+
files, _, err := resolveEmbed(dir, patterns)
1941+
return files, err
19391942
}
19401943

19411944
// resolveEmbed resolves //go:embed patterns to precise file lists.
19421945
// It sets files to the list of unique files matched (for go list),
19431946
// and it sets pmap to the more precise mapping from
19441947
// patterns to files.
1945-
func (p *Package) resolveEmbed(patterns []string) (files []string, pmap map[string][]string, err error) {
1948+
func resolveEmbed(pkgdir string, patterns []string) (files []string, pmap map[string][]string, err error) {
19461949
var pattern string
19471950
defer func() {
19481951
if err != nil {
@@ -1953,6 +1956,7 @@ func (p *Package) resolveEmbed(patterns []string) (files []string, pmap map[stri
19531956
}
19541957
}()
19551958

1959+
// TODO(rsc): All these messages need position information for better error reports.
19561960
pmap = make(map[string][]string)
19571961
have := make(map[string]int)
19581962
dirOK := make(map[string]bool)
@@ -1966,7 +1970,7 @@ func (p *Package) resolveEmbed(patterns []string) (files []string, pmap map[stri
19661970
}
19671971

19681972
// Glob to find matches.
1969-
match, err := fsys.Glob(p.Dir + string(filepath.Separator) + filepath.FromSlash(pattern))
1973+
match, err := fsys.Glob(pkgdir + string(filepath.Separator) + filepath.FromSlash(pattern))
19701974
if err != nil {
19711975
return nil, nil, err
19721976
}
@@ -1977,7 +1981,7 @@ func (p *Package) resolveEmbed(patterns []string) (files []string, pmap map[stri
19771981
// then there may be other things lying around, like symbolic links or .git directories.)
19781982
var list []string
19791983
for _, file := range match {
1980-
rel := filepath.ToSlash(file[len(p.Dir)+1:]) // file, relative to p.Dir
1984+
rel := filepath.ToSlash(file[len(pkgdir)+1:]) // file, relative to p.Dir
19811985

19821986
what := "file"
19831987
info, err := fsys.Lstat(file)
@@ -1990,13 +1994,13 @@ func (p *Package) resolveEmbed(patterns []string) (files []string, pmap map[stri
19901994

19911995
// Check that directories along path do not begin a new module
19921996
// (do not contain a go.mod).
1993-
for dir := file; len(dir) > len(p.Dir)+1 && !dirOK[dir]; dir = filepath.Dir(dir) {
1997+
for dir := file; len(dir) > len(pkgdir)+1 && !dirOK[dir]; dir = filepath.Dir(dir) {
19941998
if _, err := fsys.Stat(filepath.Join(dir, "go.mod")); err == nil {
19951999
return nil, nil, fmt.Errorf("cannot embed %s %s: in different module", what, rel)
19962000
}
19972001
if dir != file {
19982002
if info, err := fsys.Lstat(dir); err == nil && !info.IsDir() {
1999-
return nil, nil, fmt.Errorf("cannot embed %s %s: in non-directory %s", what, rel, dir[len(p.Dir)+1:])
2003+
return nil, nil, fmt.Errorf("cannot embed %s %s: in non-directory %s", what, rel, dir[len(pkgdir)+1:])
20002004
}
20012005
}
20022006
dirOK[dir] = true
@@ -2027,7 +2031,7 @@ func (p *Package) resolveEmbed(patterns []string) (files []string, pmap map[stri
20272031
if err != nil {
20282032
return err
20292033
}
2030-
rel := filepath.ToSlash(path[len(p.Dir)+1:])
2034+
rel := filepath.ToSlash(path[len(pkgdir)+1:])
20312035
name := info.Name()
20322036
if path != file && (isBadEmbedName(name) || name[0] == '.' || name[0] == '_') {
20332037
// Ignore bad names, assuming they won't go into modules.

src/cmd/go/internal/load/test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p
124124
imports = append(imports, p1)
125125
}
126126
var err error
127-
p.TestEmbedFiles, testEmbed, err = p.resolveEmbed(p.TestEmbedPatterns)
127+
p.TestEmbedFiles, testEmbed, err = resolveEmbed(p.Dir, p.TestEmbedPatterns)
128128
if err != nil && ptestErr == nil {
129129
ptestErr = &PackageError{
130130
ImportStack: stk.Copy(),
@@ -147,7 +147,7 @@ func TestPackagesAndErrors(ctx context.Context, p *Package, cover *TestCover) (p
147147
}
148148
p.XTestImports[i] = p1.ImportPath
149149
}
150-
p.XTestEmbedFiles, xtestEmbed, err = p.resolveEmbed(p.XTestEmbedPatterns)
150+
p.XTestEmbedFiles, xtestEmbed, err = resolveEmbed(p.Dir, p.XTestEmbedPatterns)
151151
if err != nil && pxtestErr == nil {
152152
pxtestErr = &PackageError{
153153
ImportStack: stk.Copy(),

src/cmd/go/internal/modcmd/vendor.go

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ package modcmd
77
import (
88
"bytes"
99
"context"
10+
"errors"
1011
"fmt"
12+
"go/build"
1113
"io"
1214
"io/fs"
1315
"os"
@@ -19,7 +21,9 @@ import (
1921
"cmd/go/internal/cfg"
2022
"cmd/go/internal/fsys"
2123
"cmd/go/internal/imports"
24+
"cmd/go/internal/load"
2225
"cmd/go/internal/modload"
26+
"cmd/go/internal/str"
2327

2428
"golang.org/x/mod/module"
2529
"golang.org/x/mod/semver"
@@ -182,19 +186,76 @@ func moduleLine(m, r module.Version) string {
182186
}
183187

184188
func vendorPkg(vdir, pkg string) {
189+
// TODO(#42504): Instead of calling modload.ImportMap then build.ImportDir,
190+
// just call load.PackagesAndErrors. To do that, we need to add a good way
191+
// to ignore build constraints.
185192
realPath := modload.ImportMap(pkg)
186193
if realPath != pkg && modload.ImportMap(realPath) != "" {
187194
fmt.Fprintf(os.Stderr, "warning: %s imported as both %s and %s; making two copies.\n", realPath, realPath, pkg)
188195
}
189196

197+
copiedFiles := make(map[string]bool)
190198
dst := filepath.Join(vdir, pkg)
191199
src := modload.PackageDir(realPath)
192200
if src == "" {
193201
fmt.Fprintf(os.Stderr, "internal error: no pkg for %s -> %s\n", pkg, realPath)
194202
}
195-
copyDir(dst, src, matchPotentialSourceFile)
203+
copyDir(dst, src, matchPotentialSourceFile, copiedFiles)
196204
if m := modload.PackageModule(realPath); m.Path != "" {
197-
copyMetadata(m.Path, realPath, dst, src)
205+
copyMetadata(m.Path, realPath, dst, src, copiedFiles)
206+
}
207+
208+
ctx := build.Default
209+
ctx.UseAllFiles = true
210+
bp, err := ctx.ImportDir(src, build.IgnoreVendor)
211+
// Because UseAllFiles is set on the build.Context, it's possible ta get
212+
// a MultiplePackageError on an otherwise valid package: the package could
213+
// have different names for GOOS=windows and GOOS=mac for example. On the
214+
// other hand if there's a NoGoError, the package might have source files
215+
// specifying "// +build ignore" those packages should be skipped because
216+
// embeds from ignored files can't be used.
217+
// TODO(#42504): Find a better way to avoid errors from ImportDir. We'll
218+
// need to figure this out when we switch to PackagesAndErrors as per the
219+
// TODO above.
220+
var multiplePackageError *build.MultiplePackageError
221+
var noGoError *build.NoGoError
222+
if err != nil {
223+
if errors.As(err, &noGoError) {
224+
return // No source files in this package are built. Skip embeds in ignored files.
225+
} else if !errors.As(err, &multiplePackageError) { // multiplePackgeErrors are okay, but others are not.
226+
base.Fatalf("internal error: failed to find embedded files of %s: %v\n", pkg, err)
227+
}
228+
}
229+
embedPatterns := str.StringList(bp.EmbedPatterns, bp.TestEmbedPatterns, bp.XTestEmbedPatterns)
230+
embeds, err := load.ResolveEmbed(bp.Dir, embedPatterns)
231+
if err != nil {
232+
base.Fatalf("go mod vendor: %v", err)
233+
}
234+
for _, embed := range embeds {
235+
embedDst := filepath.Join(dst, embed)
236+
if copiedFiles[embedDst] {
237+
continue
238+
}
239+
240+
// Copy the file as is done by copyDir below.
241+
r, err := os.Open(filepath.Join(src, embed))
242+
if err != nil {
243+
base.Fatalf("go mod vendor: %v", err)
244+
}
245+
if err := os.MkdirAll(filepath.Dir(embedDst), 0777); err != nil {
246+
base.Fatalf("go mod vendor: %v", err)
247+
}
248+
w, err := os.Create(embedDst)
249+
if err != nil {
250+
base.Fatalf("go mod vendor: %v", err)
251+
}
252+
if _, err := io.Copy(w, r); err != nil {
253+
base.Fatalf("go mod vendor: %v", err)
254+
}
255+
r.Close()
256+
if err := w.Close(); err != nil {
257+
base.Fatalf("go mod vendor: %v", err)
258+
}
198259
}
199260
}
200261

@@ -207,14 +268,14 @@ var copiedMetadata = make(map[metakey]bool)
207268

208269
// copyMetadata copies metadata files from parents of src to parents of dst,
209270
// stopping after processing the src parent for modPath.
210-
func copyMetadata(modPath, pkg, dst, src string) {
271+
func copyMetadata(modPath, pkg, dst, src string, copiedFiles map[string]bool) {
211272
for parent := 0; ; parent++ {
212273
if copiedMetadata[metakey{modPath, dst}] {
213274
break
214275
}
215276
copiedMetadata[metakey{modPath, dst}] = true
216277
if parent > 0 {
217-
copyDir(dst, src, matchMetadata)
278+
copyDir(dst, src, matchMetadata, copiedFiles)
218279
}
219280
if modPath == pkg {
220281
break
@@ -282,7 +343,7 @@ func matchPotentialSourceFile(dir string, info fs.DirEntry) bool {
282343
}
283344

284345
// copyDir copies all regular files satisfying match(info) from src to dst.
285-
func copyDir(dst, src string, match func(dir string, info fs.DirEntry) bool) {
346+
func copyDir(dst, src string, match func(dir string, info fs.DirEntry) bool, copiedFiles map[string]bool) {
286347
files, err := os.ReadDir(src)
287348
if err != nil {
288349
base.Fatalf("go mod vendor: %v", err)
@@ -294,11 +355,14 @@ func copyDir(dst, src string, match func(dir string, info fs.DirEntry) bool) {
294355
if file.IsDir() || !file.Type().IsRegular() || !match(src, file) {
295356
continue
296357
}
358+
copiedFiles[file.Name()] = true
297359
r, err := os.Open(filepath.Join(src, file.Name()))
298360
if err != nil {
299361
base.Fatalf("go mod vendor: %v", err)
300362
}
301-
w, err := os.Create(filepath.Join(dst, file.Name()))
363+
dstPath := filepath.Join(dst, file.Name())
364+
copiedFiles[dstPath] = true
365+
w, err := os.Create(dstPath)
302366
if err != nil {
303367
base.Fatalf("go mod vendor: %v", err)
304368
}

src/cmd/go/testdata/script/embed.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ go list -f '{{.EmbedPatterns}}'
33
stdout '\[x\*t\*t\]'
44
go list -f '{{.EmbedFiles}}'
55
stdout '\[x.txt\]'
6+
go list -test -f '{{.TestEmbedPatterns}}'
7+
stdout '\[y\*t\*t\]'
8+
go list -test -f '{{.TestEmbedFiles}}'
9+
stdout '\[y.txt\]'
10+
go list -test -f '{{.XTestEmbedPatterns}}'
11+
stdout '\[z\*t\*t\]'
12+
go list -test -f '{{.XTestEmbedFiles}}'
13+
stdout '\[z.txt\]'
614

715
# build embeds x.txt
816
go build -x
@@ -58,6 +66,22 @@ import "embed"
5866
//go:embed x*t*t
5967
var X embed.FS
6068

69+
-- x_test.go --
70+
package p
71+
72+
import "embed"
73+
74+
//go:embed y*t*t
75+
var Y string
76+
77+
-- x_x_test.go --
78+
package p_test
79+
80+
import "embed"
81+
82+
//go:embed z*t*t
83+
var Z string
84+
6185
-- x.go2 --
6286
package p
6387

@@ -69,6 +93,8 @@ var X embed.FS
6993
-- x.txt --
7094
hello
7195

96+
-- y.txt --
97+
-- z.txt --
7298
-- x.txt2 --
7399
not hello
74100

0 commit comments

Comments
 (0)