First version of a tool to list used dependencies and their licenses.
This commit is contained in:
parent
f269c95ce4
commit
0a4ed3a95f
40
cmd/golicenses/input.go
Normal file
40
cmd/golicenses/input.go
Normal file
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ModuleDependency struct {
|
||||
Path string
|
||||
Version string
|
||||
Dir string
|
||||
Indirect bool
|
||||
License string
|
||||
}
|
||||
|
||||
func pareseGoMod() []ModuleDependency {
|
||||
// Get list of all modules
|
||||
cmd := exec.Command("go", "list", "-m", "-json", "all")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting module list: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Split the JSON objects (one per line)
|
||||
modules := []ModuleDependency{}
|
||||
decoder := json.NewDecoder(strings.NewReader(string(output)))
|
||||
for decoder.More() {
|
||||
var mod ModuleDependency
|
||||
if err := decoder.Decode(&mod); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error decoding module: %v\n", err)
|
||||
continue
|
||||
}
|
||||
modules = append(modules, mod)
|
||||
}
|
||||
return modules
|
||||
}
|
17
cmd/golicenses/main.go
Normal file
17
cmd/golicenses/main.go
Normal file
@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
modules := pareseGoMod()
|
||||
module := NewModule(modules)
|
||||
module.GenerateLicenseNames()
|
||||
module.DumpOverview()
|
||||
|
||||
module.DumpText()
|
||||
}
|
||||
|
||||
func truncateString(s string, length int) string {
|
||||
if len(s) <= length {
|
||||
return s
|
||||
}
|
||||
return s[:length-3] + "..."
|
||||
}
|
185
cmd/golicenses/output.go
Normal file
185
cmd/golicenses/output.go
Normal file
@ -0,0 +1,185 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LicenseHash string
|
||||
|
||||
type License struct {
|
||||
LicenseType string
|
||||
Text string
|
||||
// unique name of a license.
|
||||
LicenseName string
|
||||
}
|
||||
|
||||
func hash(data string) string {
|
||||
hasher := sha512.New()
|
||||
hasher.Write([]byte(data))
|
||||
hash := hasher.Sum(nil)
|
||||
return base64.StdEncoding.EncodeToString(hash)
|
||||
}
|
||||
|
||||
func detectLicenseType(content string) string {
|
||||
content = strings.ToLower(content)
|
||||
licenses := map[string]string{
|
||||
"mit": "MIT License",
|
||||
"apache license": "Apache License",
|
||||
"bsd": "BSD License",
|
||||
"gnu general public": "GPL",
|
||||
"mozilla public": "Mozilla Public License",
|
||||
"isc": "ISC License",
|
||||
"creative commons": "Creative Commons",
|
||||
"unlicense": "Unlicense",
|
||||
}
|
||||
|
||||
for key, license := range licenses {
|
||||
if strings.Contains(content, key) {
|
||||
return license
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewLicense(text string) *License {
|
||||
licenseType := detectLicenseType(text)
|
||||
return &License{
|
||||
LicenseType: licenseType,
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
|
||||
type Licenses map[string]*License
|
||||
|
||||
type Dependency struct {
|
||||
Module string
|
||||
Version string
|
||||
Direct bool
|
||||
License *License
|
||||
}
|
||||
|
||||
func NewDependency(module string, version string, direct bool, license *License) *Dependency {
|
||||
return &Dependency{
|
||||
Module: module,
|
||||
Version: version,
|
||||
Direct: direct,
|
||||
License: license,
|
||||
}
|
||||
}
|
||||
|
||||
type Dependencies []*Dependency
|
||||
|
||||
type Module struct {
|
||||
Licenses Licenses
|
||||
Dependencies Dependencies
|
||||
}
|
||||
|
||||
func NewModule(modules []ModuleDependency) *Module {
|
||||
module := &Module{
|
||||
Licenses: Licenses{},
|
||||
Dependencies: Dependencies{},
|
||||
}
|
||||
for _, mod := range modules {
|
||||
if mod.Dir == "" {
|
||||
continue // Skip modules without local copies
|
||||
}
|
||||
license := module.findLicense(mod.Dir)
|
||||
module.Licenses[hash(license.Text)] = license
|
||||
dependency := NewDependency(mod.Path, mod.Version, !mod.Indirect, license)
|
||||
module.Dependencies = append(module.Dependencies, dependency)
|
||||
}
|
||||
sort.Slice(module.Dependencies, func(i, j int) bool {
|
||||
direct1 := module.Dependencies[i].Direct
|
||||
direct2 := module.Dependencies[j].Direct
|
||||
if direct1 != direct2 {
|
||||
if direct1 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return module.Dependencies[i].Module < module.Dependencies[j].Module
|
||||
})
|
||||
|
||||
return module
|
||||
}
|
||||
|
||||
func (module Module) GenerateLicenseNames() {
|
||||
usedNames := make(map[string]bool)
|
||||
for _, license := range module.Licenses {
|
||||
basename := strings.ReplaceAll(license.LicenseType, " ", "_")
|
||||
i := 0
|
||||
name := fmt.Sprintf("%s-%d", basename, i)
|
||||
for usedNames[name] {
|
||||
i++
|
||||
name = fmt.Sprintf("%s-%d", basename, i)
|
||||
}
|
||||
license.LicenseName = name
|
||||
usedNames[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (module Module) DumpOverview() {
|
||||
// Find and print license information for each module
|
||||
fmt.Printf("%-50s %-20s %-10s %-20s %-20s\n", "MODULE", "VERSION", "DIRECT", "LICENSE", "HASH")
|
||||
fmt.Println()
|
||||
for _, dependency := range module.Dependencies {
|
||||
fmt.Printf("%-50s %-20s %-10v %-20s %-20s\n",
|
||||
truncateString(dependency.Module, 49),
|
||||
truncateString(dependency.Version, 19),
|
||||
dependency.Direct,
|
||||
dependency.License.LicenseType,
|
||||
dependency.License.LicenseName)
|
||||
}
|
||||
}
|
||||
|
||||
func (module Module) DumpText() {
|
||||
for _, dependency := range module.Dependencies {
|
||||
fmt.Println(strings.Repeat("=", 80))
|
||||
if dependency.Direct {
|
||||
fmt.Printf("Direct dependency")
|
||||
} else {
|
||||
fmt.Printf("Indirect dependency")
|
||||
}
|
||||
fmt.Printf(" %s %s\n", dependency.Module, dependency.Version)
|
||||
fmt.Printf("LICENSE\n\n %s\n\n",
|
||||
dependency.License.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func (module *Module) findLicense(dir string) *License {
|
||||
licenseFiles := []string{
|
||||
"LICENSE",
|
||||
"LICENSE.txt",
|
||||
"LICENSE.md",
|
||||
"license",
|
||||
"license.txt",
|
||||
"license.md",
|
||||
}
|
||||
|
||||
for _, file := range licenseFiles {
|
||||
path := filepath.Join(dir, file)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
// Read first line of license file
|
||||
contentBytes, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
content := string(contentBytes)
|
||||
content = strings.TrimSpace(content)
|
||||
if len(content) > 0 {
|
||||
hashcode := hash(content)
|
||||
license := module.Licenses[hashcode]
|
||||
if license != nil {
|
||||
return license
|
||||
}
|
||||
return NewLicense(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user