summaryrefslogtreecommitdiff
path: root/pkgtools/pkglint/files/licenses.go
blob: ffb6378f88acc4eda05f3a89be3e9b5a1bdbe69b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package main

import (
	"io/ioutil"
)

//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 (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
	}

	licensedir := G.globalData.Pkgsrcdir + "/licenses"
	files, _ := ioutil.ReadDir(licensedir)
	for _, licensefile := range files {
		licensename := licensefile.Name()
		licensepath := licensedir + "/" + licensename
		if fileExists(licensepath) {
			if !G.UsedLicenses[licensename] {
				NewLineWhole(licensepath).Warn0("This license seems to be unused.")
			}
		}
	}
}

type LicenseChecker struct {
	MkLine *MkLine
}

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)
}

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)))")
	}
}