Merge pull request #12774 from cli/babakks/isolate-generated-licenses
fix(licenses): isolate generated licenses per platform (os/arch)
This commit is contained in:
commit
7a590331b5
25 changed files with 263 additions and 98 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -18,6 +18,10 @@
|
|||
# Windows resource files
|
||||
/cmd/gh/*.syso
|
||||
|
||||
# Third-party licenses
|
||||
/internal/licenses/embed/*/*
|
||||
!/internal/licenses/embed/*/PLACEHOLDER
|
||||
|
||||
# VS Code
|
||||
.vscode
|
||||
|
||||
|
|
|
|||
0
internal/licenses/embed/darwin-amd64/PLACEHOLDER
Normal file
0
internal/licenses/embed/darwin-amd64/PLACEHOLDER
Normal file
0
internal/licenses/embed/darwin-arm64/PLACEHOLDER
Normal file
0
internal/licenses/embed/darwin-arm64/PLACEHOLDER
Normal file
0
internal/licenses/embed/linux-386/PLACEHOLDER
Normal file
0
internal/licenses/embed/linux-386/PLACEHOLDER
Normal file
0
internal/licenses/embed/linux-amd64/PLACEHOLDER
Normal file
0
internal/licenses/embed/linux-amd64/PLACEHOLDER
Normal file
0
internal/licenses/embed/linux-arm/PLACEHOLDER
Normal file
0
internal/licenses/embed/linux-arm/PLACEHOLDER
Normal file
0
internal/licenses/embed/linux-arm64/PLACEHOLDER
Normal file
0
internal/licenses/embed/linux-arm64/PLACEHOLDER
Normal file
|
|
@ -1 +0,0 @@
|
|||
License information is only available in official release builds.
|
||||
|
|
@ -1 +0,0 @@
|
|||
placeholder
|
||||
0
internal/licenses/embed/windows-386/PLACEHOLDER
Normal file
0
internal/licenses/embed/windows-386/PLACEHOLDER
Normal file
0
internal/licenses/embed/windows-amd64/PLACEHOLDER
Normal file
0
internal/licenses/embed/windows-amd64/PLACEHOLDER
Normal file
0
internal/licenses/embed/windows-arm64/PLACEHOLDER
Normal file
0
internal/licenses/embed/windows-arm64/PLACEHOLDER
Normal file
8
internal/licenses/embed_darwin_amd64.go
Normal file
8
internal/licenses/embed_darwin_amd64.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package licenses
|
||||
|
||||
import "embed"
|
||||
|
||||
const rootDir = "embed/darwin-amd64"
|
||||
|
||||
//go:embed all:embed/darwin-amd64
|
||||
var embedFS embed.FS
|
||||
8
internal/licenses/embed_darwin_arm64.go
Normal file
8
internal/licenses/embed_darwin_arm64.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package licenses
|
||||
|
||||
import "embed"
|
||||
|
||||
const rootDir = "embed/darwin-arm64"
|
||||
|
||||
//go:embed all:embed/darwin-arm64
|
||||
var embedFS embed.FS
|
||||
15
internal/licenses/embed_default.go
Normal file
15
internal/licenses/embed_default.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// This file is necessary to allow building on platforms that we do not have
|
||||
// official release builds for. Without this, `go build` or `go install` calls
|
||||
// would fail due to undefined symbols that are expected to be included in the
|
||||
// build.
|
||||
|
||||
//go:build !(darwin && (amd64 || arm64)) && !(linux && (386 || amd64 || arm || arm64)) && !(windows && (386 || amd64 || arm64))
|
||||
|
||||
package licenses
|
||||
|
||||
import "embed"
|
||||
|
||||
const rootDir = ""
|
||||
|
||||
// embedFS is left empty to indicate there's no embedded content.
|
||||
var embedFS embed.FS
|
||||
8
internal/licenses/embed_linux_386.go
Normal file
8
internal/licenses/embed_linux_386.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package licenses
|
||||
|
||||
import "embed"
|
||||
|
||||
const rootDir = "embed/linux-386"
|
||||
|
||||
//go:embed all:embed/linux-386
|
||||
var embedFS embed.FS
|
||||
8
internal/licenses/embed_linux_amd64.go
Normal file
8
internal/licenses/embed_linux_amd64.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package licenses
|
||||
|
||||
import "embed"
|
||||
|
||||
const rootDir = "embed/linux-amd64"
|
||||
|
||||
//go:embed all:embed/linux-amd64
|
||||
var embedFS embed.FS
|
||||
8
internal/licenses/embed_linux_arm.go
Normal file
8
internal/licenses/embed_linux_arm.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package licenses
|
||||
|
||||
import "embed"
|
||||
|
||||
const rootDir = "embed/linux-arm"
|
||||
|
||||
//go:embed all:embed/linux-arm
|
||||
var embedFS embed.FS
|
||||
8
internal/licenses/embed_linux_arm64.go
Normal file
8
internal/licenses/embed_linux_arm64.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package licenses
|
||||
|
||||
import "embed"
|
||||
|
||||
const rootDir = "embed/linux-arm64"
|
||||
|
||||
//go:embed all:embed/linux-arm64
|
||||
var embedFS embed.FS
|
||||
8
internal/licenses/embed_windows_386.go
Normal file
8
internal/licenses/embed_windows_386.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package licenses
|
||||
|
||||
import "embed"
|
||||
|
||||
const rootDir = "embed/windows-386"
|
||||
|
||||
//go:embed all:embed/windows-386
|
||||
var embedFS embed.FS
|
||||
8
internal/licenses/embed_windows_amd64.go
Normal file
8
internal/licenses/embed_windows_amd64.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package licenses
|
||||
|
||||
import "embed"
|
||||
|
||||
const rootDir = "embed/windows-amd64"
|
||||
|
||||
//go:embed all:embed/windows-amd64
|
||||
var embedFS embed.FS
|
||||
8
internal/licenses/embed_windows_arm64.go
Normal file
8
internal/licenses/embed_windows_arm64.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package licenses
|
||||
|
||||
import "embed"
|
||||
|
||||
const rootDir = "embed/windows-arm64"
|
||||
|
||||
//go:embed all:embed/windows-arm64
|
||||
var embedFS embed.FS
|
||||
|
|
@ -1,28 +1,31 @@
|
|||
package licenses
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed embed/report.txt
|
||||
var report string
|
||||
|
||||
//go:embed all:embed/third-party
|
||||
var thirdParty embed.FS
|
||||
|
||||
// Content returns the full license report, including the main report and all
|
||||
// third-party licenses.
|
||||
func Content() string {
|
||||
return content(report, thirdParty, "embed/third-party")
|
||||
return content(embedFS, rootDir)
|
||||
}
|
||||
|
||||
func content(report string, thirdPartyFS fs.ReadFileFS, root string) string {
|
||||
func content(embedFS fs.ReadFileFS, rootDir string) string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(report)
|
||||
reportPath := path.Join(rootDir, "report.txt")
|
||||
thirdPartyPath := path.Join(rootDir, "third-party")
|
||||
|
||||
report, err := fs.ReadFile(embedFS, reportPath)
|
||||
if err != nil {
|
||||
return "License information is only available in official release builds.\n"
|
||||
}
|
||||
|
||||
b.Write(report)
|
||||
b.WriteString("\n")
|
||||
|
||||
// Walk the third-party directory and output each license/notice file
|
||||
|
|
@ -32,8 +35,13 @@ func content(report string, thirdPartyFS fs.ReadFileFS, root string) string {
|
|||
files []string
|
||||
}
|
||||
|
||||
thirdPartyFS, err := fs.Sub(embedFS, thirdPartyPath)
|
||||
if err != nil {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
modules := map[string]*moduleFiles{}
|
||||
fs.WalkDir(thirdPartyFS, root, func(filePath string, d fs.DirEntry, err error) error {
|
||||
fs.WalkDir(thirdPartyFS, ".", func(filePath string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read embedded file %s: %w", filePath, err)
|
||||
}
|
||||
|
|
@ -42,18 +50,11 @@ func content(report string, thirdPartyFS fs.ReadFileFS, root string) string {
|
|||
return nil
|
||||
}
|
||||
|
||||
name := d.Name()
|
||||
if name == "PLACEHOLDER" {
|
||||
return nil
|
||||
dir := path.Dir(filePath)
|
||||
if _, ok := modules[dir]; !ok {
|
||||
modules[dir] = &moduleFiles{path: dir}
|
||||
}
|
||||
|
||||
// Module path is the directory relative to root
|
||||
dir := filepath.Dir(filepath.FromSlash(filePath))
|
||||
rel, _ := filepath.Rel(filepath.FromSlash(root), dir)
|
||||
if _, ok := modules[rel]; !ok {
|
||||
modules[rel] = &moduleFiles{path: rel}
|
||||
}
|
||||
modules[rel].files = append(modules[rel].files, filePath)
|
||||
modules[dir].files = append(modules[dir].files, filePath)
|
||||
return nil
|
||||
})
|
||||
|
||||
|
|
@ -71,7 +72,7 @@ func content(report string, thirdPartyFS fs.ReadFileFS, root string) string {
|
|||
b.WriteString("================================================================================\n\n")
|
||||
|
||||
for _, filePath := range mod.files {
|
||||
data, err := thirdPartyFS.ReadFile(filePath)
|
||||
data, err := fs.ReadFile(thirdPartyFS, filePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,85 +1,160 @@
|
|||
package licenses
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"io/fs"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestContent_reportOnly(t *testing.T) {
|
||||
report := "dep1 (v1.0.0) - MIT - https://example.com\n"
|
||||
fsys := fstest.MapFS{
|
||||
"third-party/PLACEHOLDER": &fstest.MapFile{Data: []byte("placeholder")},
|
||||
}
|
||||
|
||||
actualContent := content(report, fsys, "third-party")
|
||||
|
||||
require.True(t, strings.HasPrefix(actualContent, report), "expected output to start with report")
|
||||
require.NotContains(t, actualContent, "PLACEHOLDER")
|
||||
require.NotContains(t, actualContent, "====")
|
||||
func TestContent(t *testing.T) {
|
||||
// This test is to ensure that we don't accidentally commit actual license
|
||||
// files in the repo. The embedded content is only included in release builds,
|
||||
// so in a normal test build we should get a default message.
|
||||
require.Equal(t, "License information is only available in official release builds.\n", Content())
|
||||
}
|
||||
|
||||
func TestContent_singleModule(t *testing.T) {
|
||||
report := "example.com/mod (v1.0.0) - MIT - https://example.com\n"
|
||||
fsys := fstest.MapFS{
|
||||
"third-party/example.com/mod/LICENSE": &fstest.MapFile{
|
||||
Data: []byte("MIT License\n\nCopyright (c) 2024"),
|
||||
func TestContent_tableTests(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fsys fstest.MapFS
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "report only",
|
||||
fsys: fstest.MapFS{
|
||||
"embed/os-arch/PLACEHOLDER": &fstest.MapFile{}, // Checked-in placeholder, so it's always there.
|
||||
"embed/os-arch/report.txt": &fstest.MapFile{Data: []byte("dep1 (v1.0.0) - MIT - https://example.com\n")},
|
||||
},
|
||||
expected: heredoc.Doc(`
|
||||
dep1 (v1.0.0) - MIT - https://example.com
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "empty third-party dir",
|
||||
fsys: fstest.MapFS{
|
||||
"embed/os-arch/PLACEHOLDER": &fstest.MapFile{}, // Checked-in placeholder, so it's always there.
|
||||
"embed/os-arch/report.txt": &fstest.MapFile{Data: []byte("dep1 (v1.0.0) - MIT - https://example.com\n")},
|
||||
"embed/os-arch/third-party": &fstest.MapFile{Data: []byte{}, Mode: fs.ModeDir},
|
||||
},
|
||||
expected: heredoc.Doc(`
|
||||
dep1 (v1.0.0) - MIT - https://example.com
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "unknown file at root ignored",
|
||||
fsys: fstest.MapFS{
|
||||
"embed/os-arch/PLACEHOLDER": &fstest.MapFile{}, // Checked-in placeholder, so it's always there.
|
||||
"embed/os-arch/report.txt": &fstest.MapFile{Data: []byte("dep1 (v1.0.0) - MIT - https://example.com\n")},
|
||||
"embed/os-arch/unknown": &fstest.MapFile{
|
||||
Data: []byte("MIT License\n\nCopyright (c) 2024"),
|
||||
},
|
||||
},
|
||||
expected: heredoc.Doc(`
|
||||
dep1 (v1.0.0) - MIT - https://example.com
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "unknown directory at root ignored",
|
||||
fsys: fstest.MapFS{
|
||||
"embed/os-arch/PLACEHOLDER": &fstest.MapFile{}, // Checked-in placeholder, so it's always there.
|
||||
"embed/os-arch/report.txt": &fstest.MapFile{Data: []byte("dep1 (v1.0.0) - MIT - https://example.com\n")},
|
||||
"embed/os-arch/unknown/example.com/mod/LICENSE": &fstest.MapFile{
|
||||
Data: []byte("MIT License\n\nCopyright (c) 2024"),
|
||||
},
|
||||
},
|
||||
expected: heredoc.Doc(`
|
||||
dep1 (v1.0.0) - MIT - https://example.com
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "single module",
|
||||
fsys: fstest.MapFS{
|
||||
"embed/os-arch/PLACEHOLDER": &fstest.MapFile{}, // Checked-in placeholder, so it's always there.
|
||||
"embed/os-arch/report.txt": &fstest.MapFile{Data: []byte("example.com/mod (v1.0.0) - MIT - https://example.com\n")},
|
||||
"embed/os-arch/third-party/example.com/mod/LICENSE": &fstest.MapFile{
|
||||
Data: []byte("MIT License\n\nCopyright (c) 2024"),
|
||||
},
|
||||
},
|
||||
expected: heredoc.Doc(`
|
||||
example.com/mod (v1.0.0) - MIT - https://example.com
|
||||
|
||||
================================================================================
|
||||
example.com/mod
|
||||
================================================================================
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "multiple modules sorted alphabetically",
|
||||
fsys: fstest.MapFS{
|
||||
"embed/os-arch/PLACEHOLDER": &fstest.MapFile{}, // Checked-in placeholder, so it's always there.
|
||||
"embed/os-arch/report.txt": &fstest.MapFile{Data: []byte("example.com/mod (v1.0.0) - MIT - https://example.com\n")},
|
||||
"embed/os-arch/third-party/github.com/zzz/pkg/LICENSE": &fstest.MapFile{
|
||||
Data: []byte("ZZZ License"),
|
||||
},
|
||||
"embed/os-arch/third-party/github.com/aaa/pkg/LICENSE": &fstest.MapFile{
|
||||
Data: []byte("AAA License"),
|
||||
},
|
||||
},
|
||||
expected: heredoc.Doc(`
|
||||
example.com/mod (v1.0.0) - MIT - https://example.com
|
||||
|
||||
================================================================================
|
||||
github.com/aaa/pkg
|
||||
================================================================================
|
||||
|
||||
AAA License
|
||||
|
||||
================================================================================
|
||||
github.com/zzz/pkg
|
||||
================================================================================
|
||||
|
||||
ZZZ License
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "license and notice files",
|
||||
fsys: fstest.MapFS{
|
||||
"embed/os-arch/PLACEHOLDER": &fstest.MapFile{}, // Checked-in placeholder, so it's always there.
|
||||
"embed/os-arch/report.txt": &fstest.MapFile{Data: []byte("example.com/mod (v1.0.0) - MIT - https://example.com\n")},
|
||||
"embed/os-arch/third-party/example.com/mod/LICENSE": &fstest.MapFile{
|
||||
Data: []byte("Apache License 2.0"),
|
||||
},
|
||||
"embed/os-arch/third-party/example.com/mod/NOTICE": &fstest.MapFile{
|
||||
Data: []byte("Copyright 2024 Example Corp"),
|
||||
},
|
||||
},
|
||||
expected: heredoc.Doc(`
|
||||
example.com/mod (v1.0.0) - MIT - https://example.com
|
||||
|
||||
================================================================================
|
||||
example.com/mod
|
||||
================================================================================
|
||||
|
||||
Apache License 2.0
|
||||
|
||||
Copyright 2024 Example Corp
|
||||
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
actualContent := content(report, fsys, "third-party")
|
||||
|
||||
require.Contains(t, actualContent, filepath.FromSlash("example.com/mod"))
|
||||
require.Contains(t, actualContent, "MIT License")
|
||||
}
|
||||
|
||||
func TestContent_multipleModulesSortedAlphabetically(t *testing.T) {
|
||||
report := "header\n"
|
||||
fsys := fstest.MapFS{
|
||||
"third-party/github.com/zzz/pkg/LICENSE": &fstest.MapFile{
|
||||
Data: []byte("ZZZ License"),
|
||||
},
|
||||
"third-party/github.com/aaa/pkg/LICENSE": &fstest.MapFile{
|
||||
Data: []byte("AAA License"),
|
||||
},
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := content(tt.fsys, "embed/os-arch")
|
||||
require.Equal(t, tt.expected, got)
|
||||
})
|
||||
}
|
||||
|
||||
actualContent := content(report, fsys, "third-party")
|
||||
|
||||
aIdx := strings.Index(actualContent, filepath.FromSlash("github.com/aaa/pkg"))
|
||||
zIdx := strings.Index(actualContent, filepath.FromSlash("github.com/zzz/pkg"))
|
||||
require.NotEqual(t, -1, aIdx, "expected aaa module in output")
|
||||
require.NotEqual(t, -1, zIdx, "expected zzz module in output")
|
||||
require.Less(t, aIdx, zIdx, "expected modules to be sorted alphabetically")
|
||||
}
|
||||
|
||||
func TestContent_licenseAndNoticeFiles(t *testing.T) {
|
||||
report := "header\n"
|
||||
fsys := fstest.MapFS{
|
||||
"third-party/example.com/mod/LICENSE": &fstest.MapFile{
|
||||
Data: []byte("Apache License 2.0"),
|
||||
},
|
||||
"third-party/example.com/mod/NOTICE": &fstest.MapFile{
|
||||
Data: []byte("Copyright 2024 Example Corp"),
|
||||
},
|
||||
}
|
||||
|
||||
actualContent := content(report, fsys, "third-party")
|
||||
|
||||
require.Contains(t, actualContent, "Apache License 2.0")
|
||||
require.Contains(t, actualContent, "Copyright 2024 Example Corp")
|
||||
}
|
||||
|
||||
func TestContent_emptyThirdPartyDir(t *testing.T) {
|
||||
report := "header\n"
|
||||
fsys := fstest.MapFS{
|
||||
"third-party/empty": &fstest.MapFile{Data: []byte("")},
|
||||
}
|
||||
|
||||
actualContent := content(report, fsys, "third-party")
|
||||
|
||||
require.True(t, strings.HasPrefix(actualContent, "header\n"), "expected output to start with report header")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# Generate third-party license information for embedding in the binary.
|
||||
#
|
||||
# Usage:
|
||||
# ./script/licenses <GOOS> <GOARCH> Generate licenses for a single platform
|
||||
# ./script/licenses <GOOS> <GOARCH> Generate licenses for a single platform
|
||||
# ./script/licenses --check Verify generation works for all release platforms
|
||||
#
|
||||
# The single-platform mode is called by goreleaser pre-build hooks to generate
|
||||
|
|
@ -75,8 +75,8 @@ if [ "$1" = "--check" ]; then
|
|||
|
||||
echo "License generation verified for all platforms."
|
||||
elif [ $# -eq 2 ]; then
|
||||
generate_licenses "$1" "$2" "internal/licenses/embed"
|
||||
echo "Licenses written to internal/licenses/embed/"
|
||||
generate_licenses "$1" "$2" "internal/licenses/embed/${1}-${2}"
|
||||
echo "Licenses written to internal/licenses/embed/${1}-${2}"
|
||||
else
|
||||
echo "Usage: $0 <GOOS> <GOARCH>"
|
||||
echo " $0 --check"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue