зеркало из https://github.com/golang/pkgsite.git
internal: add GetLicenses by path
This change introduces a new method on the DataSource interface, GetLicenses, which returns the licenses that apply for a given path, module path, and resolved version combination. A license in the current or any parent directory of the specified path applies to it. Fixes golang/go#40027 Change-Id: If91429ac12880ac3b6254bf0acd7d3ac983c93e7 Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/241718 Reviewed-by: Julie Qiu <julie@golang.org>
This commit is contained in:
Родитель
130a4e343b
Коммит
f7303dcc15
|
@ -21,6 +21,8 @@ type DataSource interface {
|
||||||
// GetImports returns a slice of import paths imported by the package
|
// GetImports returns a slice of import paths imported by the package
|
||||||
// specified by path and version.
|
// specified by path and version.
|
||||||
GetImports(ctx context.Context, pkgPath, modulePath, version string) ([]string, error)
|
GetImports(ctx context.Context, pkgPath, modulePath, version string) ([]string, error)
|
||||||
|
// GetLicenses returns licenses at the given path for given modulePath and version.
|
||||||
|
GetLicenses(ctx context.Context, fullPath, modulePath, resolvedVersion string) ([]*licenses.License, error)
|
||||||
// GetModuleInfo returns the ModuleInfo corresponding to modulePath and
|
// GetModuleInfo returns the ModuleInfo corresponding to modulePath and
|
||||||
// version.
|
// version.
|
||||||
GetModuleInfo(ctx context.Context, modulePath, version string) (*ModuleInfo, error)
|
GetModuleInfo(ctx context.Context, modulePath, version string) (*ModuleInfo, error)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -17,6 +18,61 @@ import (
|
||||||
"golang.org/x/pkgsite/internal/licenses"
|
"golang.org/x/pkgsite/internal/licenses"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetLicenses returns the licenses that applies to the fullPath for the given module version.
|
||||||
|
// It returns an InvalidArgument error if the module path or version is invalid.
|
||||||
|
func (db *DB) GetLicenses(ctx context.Context, fullPath, modulePath, resolvedVersion string) (_ []*licenses.License, err error) {
|
||||||
|
defer derrors.Wrap(&err, "GetLicenses(ctx, %q, %q, %q)", fullPath, modulePath, resolvedVersion)
|
||||||
|
|
||||||
|
if fullPath == "" || resolvedVersion == "" {
|
||||||
|
return nil, fmt.Errorf("neither fullpath nor resolvedVersion can be empty: %w", derrors.InvalidArgument)
|
||||||
|
}
|
||||||
|
query := `
|
||||||
|
SELECT
|
||||||
|
l.types,
|
||||||
|
l.file_path,
|
||||||
|
l.contents,
|
||||||
|
l.coverage
|
||||||
|
FROM
|
||||||
|
licenses l
|
||||||
|
INNER JOIN
|
||||||
|
paths p
|
||||||
|
ON
|
||||||
|
p.module_id=l.module_id
|
||||||
|
INNER JOIN
|
||||||
|
modules m
|
||||||
|
ON
|
||||||
|
p.module_id=m.id
|
||||||
|
WHERE
|
||||||
|
p.path = $1
|
||||||
|
AND m.module_path = $2
|
||||||
|
AND m.version = $3;`
|
||||||
|
|
||||||
|
rows, err := db.db.Query(ctx, query, fullPath, modulePath, resolvedVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
moduleLicenses, err := collectLicenses(rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var lics []*licenses.License
|
||||||
|
|
||||||
|
// The `query` returns all licenses for the module version. We need to
|
||||||
|
// filter the licenses that applies to the specified fullPath, i.e.
|
||||||
|
// A license in the current or any parent directory of the specified
|
||||||
|
// fullPath applies to it.
|
||||||
|
for _, license := range moduleLicenses {
|
||||||
|
licensePath := path.Join(modulePath, path.Dir(license.FilePath))
|
||||||
|
if strings.HasPrefix(fullPath, licensePath) {
|
||||||
|
lics = append(lics, license)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lics, nil
|
||||||
|
}
|
||||||
|
|
||||||
// LegacyGetModuleLicenses returns all licenses associated with the given module path and
|
// LegacyGetModuleLicenses returns all licenses associated with the given module path and
|
||||||
// version. These are the top-level licenses in the module zip file.
|
// version. These are the top-level licenses in the module zip file.
|
||||||
// It returns an InvalidArgument error if the module path or version is invalid.
|
// It returns an InvalidArgument error if the module path or version is invalid.
|
||||||
|
|
|
@ -6,13 +6,83 @@ package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"golang.org/x/pkgsite/internal/derrors"
|
||||||
"golang.org/x/pkgsite/internal/licenses"
|
"golang.org/x/pkgsite/internal/licenses"
|
||||||
"golang.org/x/pkgsite/internal/testing/sample"
|
"golang.org/x/pkgsite/internal/testing/sample"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestGetLicenses(t *testing.T) {
|
||||||
|
testModule := sample.Module(sample.ModulePath, "v1.2.3", "A/B")
|
||||||
|
mit := &licenses.Metadata{Types: []string{"MIT"}, FilePath: "LICENSE"}
|
||||||
|
bsd := &licenses.Metadata{Types: []string{"BSD-3-Clause"}, FilePath: "A/B/LICENSE"}
|
||||||
|
|
||||||
|
mitLicense := &licenses.License{Metadata: mit}
|
||||||
|
bsdLicense := &licenses.License{Metadata: bsd}
|
||||||
|
testModule.Licenses = []*licenses.License{bsdLicense, mitLicense}
|
||||||
|
sort.Slice(testModule.Directories, func(i, j int) bool {
|
||||||
|
return testModule.Directories[i].Path < testModule.Directories[j].Path
|
||||||
|
})
|
||||||
|
|
||||||
|
// github.com/valid/module_name
|
||||||
|
testModule.Directories[0].Licenses = []*licenses.Metadata{mit}
|
||||||
|
// github.com/valid/module_name/A
|
||||||
|
testModule.Directories[1].Licenses = []*licenses.Metadata{mit}
|
||||||
|
// github.com/valid/module_name/A/B
|
||||||
|
testModule.Directories[2].Licenses = []*licenses.Metadata{mit, bsd}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
err error
|
||||||
|
name string
|
||||||
|
fullPath string
|
||||||
|
want []*licenses.License
|
||||||
|
}{
|
||||||
|
{name: "empty path", err: derrors.InvalidArgument},
|
||||||
|
{name: "module root", fullPath: sample.ModulePath, want: []*licenses.License{testModule.Licenses[1]}},
|
||||||
|
{name: "package without license", fullPath: sample.ModulePath + "/A", want: []*licenses.License{testModule.Licenses[1]}},
|
||||||
|
{name: "package with additional license", fullPath: sample.ModulePath + "/A/B", want: testModule.Licenses},
|
||||||
|
}
|
||||||
|
|
||||||
|
defer ResetTestDB(testDB, t)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout*5)
|
||||||
|
defer cancel()
|
||||||
|
if err := testDB.InsertModule(ctx, testModule); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
got, err := testDB.GetLicenses(ctx, test.fullPath, sample.ModulePath, testModule.Version)
|
||||||
|
if !errors.Is(err, test.err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(got, func(i, j int) bool {
|
||||||
|
return got[i].FilePath < got[j].FilePath
|
||||||
|
})
|
||||||
|
sort.Slice(test.want, func(i, j int) bool {
|
||||||
|
return test.want[i].FilePath < test.want[j].FilePath
|
||||||
|
})
|
||||||
|
for i := range got {
|
||||||
|
sort.Strings(got[i].Types)
|
||||||
|
}
|
||||||
|
for i := range test.want {
|
||||||
|
sort.Strings(test.want[i].Types)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmpopt := cmpopts.IgnoreFields(licenses.License{}, "Contents")
|
||||||
|
if diff := cmp.Diff(test.want, got, cmpopt); diff != "" {
|
||||||
|
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLegacyGetModuleLicenses(t *testing.T) {
|
func TestLegacyGetModuleLicenses(t *testing.T) {
|
||||||
modulePath := "test.module"
|
modulePath := "test.module"
|
||||||
testModule := sample.Module(modulePath, "v1.2.3", "", "foo", "bar")
|
testModule := sample.Module(modulePath, "v1.2.3", "", "foo", "bar")
|
||||||
|
|
|
@ -117,6 +117,33 @@ func (ds *DataSource) GetImports(ctx context.Context, pkgPath, modulePath, versi
|
||||||
return vp.Imports, nil
|
return vp.Imports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLicenses return licenses at path for the given module path and version.
|
||||||
|
func (ds *DataSource) GetLicenses(ctx context.Context, fullPath, modulePath, resolvedVersion string) (_ []*licenses.License, err error) {
|
||||||
|
defer derrors.Wrap(&err, "GetLicenses(%q, %q, %q)", fullPath, modulePath, resolvedVersion)
|
||||||
|
v, err := ds.getModule(ctx, modulePath, resolvedVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var lics []*licenses.License
|
||||||
|
|
||||||
|
// ds.getModule() returns all licenses for the module version. We need to
|
||||||
|
// filter the licenses that applies to the specified fullPath, i.e.
|
||||||
|
// A license in the current or any parent directory of the specified
|
||||||
|
// fullPath applies to it.
|
||||||
|
for _, license := range v.Licenses {
|
||||||
|
licensePath := path.Join(modulePath, path.Dir(license.FilePath))
|
||||||
|
if strings.HasPrefix(fullPath, licensePath) {
|
||||||
|
lics = append(lics, license)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lics) == 0 {
|
||||||
|
return nil, fmt.Errorf("path %s is missing from module %s: %w", fullPath, modulePath, derrors.NotFound)
|
||||||
|
}
|
||||||
|
return lics, nil
|
||||||
|
}
|
||||||
|
|
||||||
// LegacyGetModuleLicenses returns root-level licenses detected within the module zip
|
// LegacyGetModuleLicenses returns root-level licenses detected within the module zip
|
||||||
// for modulePath and version.
|
// for modulePath and version.
|
||||||
func (ds *DataSource) LegacyGetModuleLicenses(ctx context.Context, modulePath, version string) (_ []*licenses.License, err error) {
|
func (ds *DataSource) LegacyGetModuleLicenses(ctx context.Context, modulePath, version string) (_ []*licenses.License, err error) {
|
||||||
|
|
|
@ -6,12 +6,16 @@ package proxydatasource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"github.com/google/licensecheck"
|
||||||
"golang.org/x/pkgsite/internal"
|
"golang.org/x/pkgsite/internal"
|
||||||
|
"golang.org/x/pkgsite/internal/derrors"
|
||||||
"golang.org/x/pkgsite/internal/licenses"
|
"golang.org/x/pkgsite/internal/licenses"
|
||||||
"golang.org/x/pkgsite/internal/proxy"
|
"golang.org/x/pkgsite/internal/proxy"
|
||||||
"golang.org/x/pkgsite/internal/testing/sample"
|
"golang.org/x/pkgsite/internal/testing/sample"
|
||||||
|
@ -47,9 +51,31 @@ func setup(t *testing.T) (context.Context, *DataSource, func()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
wantLicenseMD = sample.LicenseMetadata[0]
|
wantLicenseMD = sample.LicenseMetadata[0]
|
||||||
wantLicense = &licenses.License{Metadata: wantLicenseMD}
|
wantLicense = &licenses.License{Metadata: wantLicenseMD}
|
||||||
wantPackage = internal.LegacyPackage{
|
wantLicenseMIT = &licenses.License{
|
||||||
|
Metadata: &licenses.Metadata{
|
||||||
|
Types: []string{"MIT"},
|
||||||
|
FilePath: "LICENSE",
|
||||||
|
Coverage: licensecheck.Coverage{
|
||||||
|
Percent: 100,
|
||||||
|
Match: []licensecheck.Match{{Name: "MIT", Type: licensecheck.MIT, Percent: 100, End: 1049}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Contents: []byte(testhelper.MITLicense),
|
||||||
|
}
|
||||||
|
wantLicenseBSD = &licenses.License{
|
||||||
|
Metadata: &licenses.Metadata{
|
||||||
|
Types: []string{"BSD-0-Clause"},
|
||||||
|
FilePath: "qux/LICENSE",
|
||||||
|
Coverage: licensecheck.Coverage{
|
||||||
|
Percent: 100,
|
||||||
|
Match: []licensecheck.Match{{Name: "BSD-0-Clause", Type: licensecheck.BSD, Percent: 100, End: 633}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Contents: []byte(testhelper.BSD0License),
|
||||||
|
}
|
||||||
|
wantPackage = internal.LegacyPackage{
|
||||||
Path: "foo.com/bar/baz",
|
Path: "foo.com/bar/baz",
|
||||||
Name: "baz",
|
Name: "baz",
|
||||||
Imports: []string{"net/http"},
|
Imports: []string{"net/http"},
|
||||||
|
@ -144,6 +170,68 @@ func TestDataSource_GetModuleInfo(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDataSource_GetLicenses(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
testModules := []*proxy.TestModule{
|
||||||
|
{
|
||||||
|
ModulePath: "foo.com/bar",
|
||||||
|
Version: "v1.1.0",
|
||||||
|
Files: map[string]string{
|
||||||
|
"go.mod": "module foo.com/bar",
|
||||||
|
"LICENSE": testhelper.MITLicense,
|
||||||
|
"bar.go": "//Package bar provides a helpful constant.\npackage bar\nimport \"net/http\"\nconst OK = http.StatusOK",
|
||||||
|
|
||||||
|
"baz/baz.go": "//Package baz provides a helpful constant.\npackage baz\nimport \"net/http\"\nconst OK = http.StatusOK",
|
||||||
|
|
||||||
|
"qux/LICENSE": testhelper.BSD0License,
|
||||||
|
"qux/qux.go": "//Package qux provides a helpful constant.\npackage qux\nimport \"net/http\"\nconst OK = http.StatusOK",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client, teardownProxy := proxy.SetupTestProxy(t, testModules)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
ds := New(client)
|
||||||
|
teardown := func() {
|
||||||
|
teardownProxy()
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
err error
|
||||||
|
name string
|
||||||
|
fullPath string
|
||||||
|
modulePath string
|
||||||
|
want []*licenses.License
|
||||||
|
}{
|
||||||
|
{name: "no license dir", fullPath: "foo.com", modulePath: "foo.com/bar", err: derrors.NotFound},
|
||||||
|
{name: "invalid dir", fullPath: "foo.com/invalid", modulePath: "foo.com/invalid", err: derrors.NotFound},
|
||||||
|
{name: "root dir", fullPath: "foo.com/bar", modulePath: "foo.com/bar", want: []*licenses.License{wantLicenseMIT}},
|
||||||
|
{name: "package with no extra license", fullPath: "foo.com/bar/baz", modulePath: "foo.com/bar", want: []*licenses.License{wantLicenseMIT}},
|
||||||
|
{name: "package with additional license", fullPath: "foo.com/bar/qux", modulePath: "foo.com/bar", want: []*licenses.License{wantLicenseMIT, wantLicenseBSD}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
got, err := ds.GetLicenses(ctx, test.fullPath, test.modulePath, "v1.1.0")
|
||||||
|
if !errors.Is(err, test.err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(got, func(i, j int) bool {
|
||||||
|
return got[i].FilePath < got[j].FilePath
|
||||||
|
})
|
||||||
|
sort.Slice(test.want, func(i, j int) bool {
|
||||||
|
return test.want[i].FilePath < test.want[j].FilePath
|
||||||
|
})
|
||||||
|
|
||||||
|
if diff := cmp.Diff(test.want, got); diff != "" {
|
||||||
|
t.Errorf("GetLicenses diff (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDataSource_LegacyGetModuleLicenses(t *testing.T) {
|
func TestDataSource_LegacyGetModuleLicenses(t *testing.T) {
|
||||||
ctx, ds, teardown := setup(t)
|
ctx, ds, teardown := setup(t)
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
Загрузка…
Ссылка в новой задаче