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