summaryrefslogtreecommitdiff
path: root/pkgtools/pkglint/files/licenses.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkgtools/pkglint/files/licenses.go')
-rw-r--r--pkgtools/pkglint/files/licenses.go162
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)))")
+ }
}