diff options
Diffstat (limited to 'pkgtools/pkglint/files/licenses.go')
-rw-r--r-- | pkgtools/pkglint/files/licenses.go | 162 |
1 files changed, 127 insertions, 35 deletions
diff --git a/pkgtools/pkglint/files/licenses.go b/pkgtools/pkglint/files/licenses.go index 0009e6e56e4..ffb6378f88a 100644 --- a/pkgtools/pkglint/files/licenses.go +++ b/pkgtools/pkglint/files/licenses.go @@ -2,16 +2,78 @@ package main import ( "io/ioutil" - "strings" ) -func parseLicenses(licenses string) []string { - noPerl := strings.Replace(licenses, "${PERL5_LICENSE}", "gnu-gpl-v2 OR artistic", -1) - noOps := regcomp(`[()]|AND|OR`).ReplaceAllString(noPerl, "") // cheated - return splitOnSpace(strings.TrimSpace(noOps)) +//go:generate go tool yacc -p liyy -o licenseyacc.go -v licenseyacc.log license.y + +// LicenseCondition describes a complex license condition. +// It has either `Name` or `Main` set. +type LicenseCondition struct { + Name string + Main *LicenseCondition + And []*LicenseCondition + Or []*LicenseCondition +} + +func (lc *LicenseCondition) Walk(callback func(*LicenseCondition)) { + callback(lc) + if lc.Main != nil { + lc.Main.Walk(callback) + } + for _, and := range lc.And { + and.Walk(callback) + } + for _, or := range lc.Or { + or.Walk(callback) + } +} + +type licenseLexer struct { + repl *PrefixReplacer + result *LicenseCondition + error string } -func checktoplevelUnusedLicenses() { +func (lexer *licenseLexer) Lex(llval *liyySymType) int { + repl := lexer.repl + repl.AdvanceHspace() + switch { + case repl.rest == "": + return 0 + case repl.AdvanceStr("("): + return ltOPEN + case repl.AdvanceStr(")"): + return ltCLOSE + case repl.AdvanceRegexp(`^[\w-.]+`): + word := repl.m[0] + switch word { + case "AND": + return ltAND + case "OR": + return ltOR + default: + llval.Node = &LicenseCondition{Name: word} + return ltNAME + } + } + return -1 +} + +func (lexer *licenseLexer) Error(s string) { + lexer.error = s +} + +func parseLicenses(licenses string) *LicenseCondition { + expanded := resolveVariableRefs(licenses) // For ${PERL5_LICENSE} + lexer := &licenseLexer{repl: NewPrefixReplacer(expanded)} + result := liyyNewParser().Parse(lexer) + if result == 0 { + return lexer.result + } + return nil +} + +func checkToplevelUnusedLicenses() { if G.UsedLicenses == nil { return } @@ -29,38 +91,68 @@ func checktoplevelUnusedLicenses() { } } -func checklineLicense(line *MkLine, value string) { - licenses := parseLicenses(value) - for _, license := range licenses { - var licenseFile string - if G.Pkg != nil { - if licenseFileValue, ok := G.Pkg.varValue("LICENSE_FILE"); ok { - licenseFile = G.CurrentDir + "/" + resolveVarsInRelativePath(licenseFileValue, false) - } - } - if licenseFile == "" { - licenseFile = G.globalData.Pkgsrcdir + "/licenses/" + license - if G.UsedLicenses != nil { - G.UsedLicenses[license] = true - } - } +type LicenseChecker struct { + MkLine *MkLine +} - if !fileExists(licenseFile) { - line.Warn1("License file %s does not exist.", cleanpath(licenseFile)) +func (lc *LicenseChecker) Check(value string, op MkOperator) { + licenses := parseLicenses(ifelseStr(op == opAssignAppend, "append-placeholder ", "") + value) + + if licenses == nil { + if op == opAssign { + lc.MkLine.Line.Error1("Parse error for license condition %q.", value) + } else { + lc.MkLine.Line.Error1("Parse error for appended license condition %q.", value) } + return + } + + licenses.Walk(lc.checkNode) +} - switch license { - case "fee-based-commercial-use", - "no-commercial-use", - "no-profit", - "no-redistribution", - "shareware": - line.Warn1("License %q is deprecated.", license) - Explain( - "Instead of using these deprecated licenses, extract the actual", - "license from the package into the pkgsrc/licenses/ directory", - "and define LICENSE to that file name. See the pkgsrc guide,", - "keyword LICENSE, for more information.") +func (lc *LicenseChecker) checkNode(cond *LicenseCondition) { + license := cond.Name + if license == "" || license == "append-placeholder" { + return + } + + var licenseFile string + if G.Pkg != nil { + if licenseFileValue, ok := G.Pkg.varValue("LICENSE_FILE"); ok { + licenseFile = G.CurrentDir + "/" + resolveVarsInRelativePath(licenseFileValue, false) + } + } + if licenseFile == "" { + licenseFile = G.globalData.Pkgsrcdir + "/licenses/" + license + if G.UsedLicenses != nil { + G.UsedLicenses[license] = true } } + + if !fileExists(licenseFile) { + lc.MkLine.Warn1("License file %s does not exist.", cleanpath(licenseFile)) + } + + switch license { + case "fee-based-commercial-use", + "no-commercial-use", + "no-profit", + "no-redistribution", + "shareware": + lc.MkLine.Error1("License %q must not be used.", license) + Explain( + "Instead of using these deprecated licenses, extract the actual", + "license from the package into the pkgsrc/licenses/ directory", + "and define LICENSE to that file name. See the pkgsrc guide,", + "keyword LICENSE, for more information.") + } + + if len(cond.And) > 0 && len(cond.Or) > 0 { + lc.MkLine.Line.Error0("AND and OR operators in license conditions can only be combined using parentheses.") + Explain( + "Examples for valid license conditions are:", + "", + "\tlicense1 AND license2 AND (license3 OR license4)", + "\t(((license1 OR license2) AND (license3 OR license4)))") + } } |