Skip to content

Commit 1620e01

Browse files
committed
license-gather: support license inference from Bundle-License URI
1 parent 133e039 commit 1620e01

File tree

5 files changed

+193
-12
lines changed

5 files changed

+193
-12
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ Change log
192192
v1.78
193193
* chore: bump Gradle 6.7 -> 6.9.1
194194
* license-gather: ignore xml namespaces when parsing POM files (see #43)
195+
* license-gather: fix license inference from Bundle-License manifest attribute (see #48)
196+
197+
Thanks to [Florian Dreier](https://github.com/DreierF) for identifying bugs and suggesting fixes.
195198

196199
v1.77
197200
* crlf-plugin: bump jgit to 5.13.0.202109080827-r

buildSrc/subprojects/license-texts/src/main/kotlin/com/github/vlsi/gradle/license/EnumGeneratorTask.kt

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,16 @@ open class EnumGeneratorTask @Inject constructor(objectFactory: ObjectFactory) :
147147
.initializer("values().associateBy { it.id }")
148148
.build()
149149
)
150+
.addProperty(
151+
PropertySpec.builder(
152+
"uriToInstance",
153+
Map::class.asClassName()
154+
.parameterizedBy(URI::class.asClassName(), className),
155+
KModifier.PRIVATE
156+
)
157+
.initializer("values().flatMap { e -> (e.uri + e.detailsUri).map { it to e } }.toMap()")
158+
.build()
159+
)
150160
.addFunction(
151161
FunSpec.builder("fromId")
152162
.addParameter("id", String::class)
@@ -159,6 +169,30 @@ open class EnumGeneratorTask @Inject constructor(objectFactory: ObjectFactory) :
159169
.addStatement("return idToInstance[id]")
160170
.build()
161171
)
172+
.addFunction(
173+
FunSpec.builder("fromUri")
174+
.addParameter("uri", URI::class)
175+
.addStatement(
176+
"return fromUriOrNull(uri) ?: throw %T(%P)",
177+
NoSuchElementException::class,
178+
"No license found for given URI: \$uri"
179+
)
180+
.build()
181+
)
182+
.addFunction(
183+
FunSpec.builder("fromUriOrNull")
184+
.addParameter("uri", URI::class)
185+
.addStatement("return uriToInstance[uri.toHttps()]")
186+
.build()
187+
)
188+
.addFunction(
189+
FunSpec.builder("toHttps")
190+
.addModifiers(KModifier.PRIVATE)
191+
.receiver(URI::class)
192+
.returns(URI::class)
193+
.addStatement("return if (!toString().startsWith(\"http://\")) this else URI(toString().replaceFirst(\"http:\", \"https:\"))")
194+
.build()
195+
)
162196
.build()
163197
)
164198
.addProperty(
@@ -179,10 +213,21 @@ open class EnumGeneratorTask @Inject constructor(objectFactory: ObjectFactory) :
179213
TypeSpec.anonymousClassBuilder()
180214
.addSuperclassConstructorParameter("%S", it.id)
181215
.addSuperclassConstructorParameter("%S", it.name)
182-
.addSuperclassConstructorParameter("%S", it.detailsUrl)
183-
.addSuperclassConstructorParameter("arrayOf(%L)",
184-
it.seeAlso.map { url -> CodeBlock.of("%S", url.trim()) }
185-
.joinToCode(", "))
216+
.addSuperclassConstructorParameter(
217+
"%S",
218+
it.detailsUrl.replaceFirst("http://", "https://")
219+
)
220+
.addSuperclassConstructorParameter(
221+
"arrayOf(%L)",
222+
it.seeAlso
223+
.map { url ->
224+
CodeBlock.of(
225+
"%S",
226+
url.trim().replaceFirst("http://", "https://")
227+
)
228+
}
229+
.joinToCode(", ")
230+
)
186231
.build()
187232
)
188233
}

plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import com.github.vlsi.gradle.license.api.JustLicense
2222
import com.github.vlsi.gradle.license.api.License
2323
import com.github.vlsi.gradle.license.api.LicenseExpression
2424
import com.github.vlsi.gradle.license.api.LicenseExpressionParser
25+
import com.github.vlsi.gradle.license.api.OsgiBundleLicenseParser
26+
import com.github.vlsi.gradle.license.api.ParseException
2527
import com.github.vlsi.gradle.license.api.SpdxLicense
2628
import com.github.vlsi.gradle.license.api.asExpression
2729
import com.github.vlsi.gradle.license.api.text
@@ -471,6 +473,9 @@ open class GatherLicenseTask @Inject constructor(
471473
detectedLicenses: MutableMap<ComponentIdentifier, LicenseInfo>,
472474
licenseExpressionParser: LicenseExpressionParser
473475
) {
476+
val bundleLicenseParser = OsgiBundleLicenseParser(licenseExpressionParser) {
477+
SpdxLicense.fromUriOrNull(it)?.asExpression()
478+
}
474479
for (e in detectedLicenses) {
475480
if (e.value.license != null) {
476481
continue
@@ -500,14 +505,8 @@ open class GatherLicenseTask @Inject constructor(
500505
)
501506
JarFile(file).use { jar ->
502507
val bundleLicense = jar.manifest.mainAttributes.getValue("Bundle-License")
503-
if (bundleLicense == null || bundleLicense.startsWith("http://") || bundleLicense.startsWith("https://")) {
504-
// Ignore URLs here as it will fail during parsing
505-
return
506-
}
507-
val license = bundleLicense.substringBefore(";")?.let {
508-
licenseExpressionParser.parse(it)
509-
}
510-
if (license != null) {
508+
?: return@use
509+
bundleLicenseParser.parseOrNull(bundleLicense, file)?.let { license ->
511510
logger.debug("Detected license for ${e.key}: $license")
512511
e.setValue(e.value.copy(license = license))
513512
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2019 Vladimir Sitnikov <[email protected]>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package com.github.vlsi.gradle.license.api
19+
20+
import org.slf4j.LoggerFactory
21+
import java.net.URI
22+
import java.net.URISyntaxException
23+
24+
class OsgiBundleLicenseParser(
25+
private val licenseExpressionParser: LicenseExpressionParser,
26+
private val lookupLicenseByUri: (URI) -> LicenseExpression?
27+
) {
28+
private val logger = LoggerFactory.getLogger(OsgiBundleLicenseParser::class.java)
29+
30+
fun parseOrNull(bundleLicense: String, context: Any): LicenseExpression? {
31+
return if (bundleLicense.contains(',')) {
32+
logger.info(
33+
"Ignoring Bundle-License '{}' in {} since it contains multiple license references",
34+
bundleLicense,
35+
context
36+
)
37+
null
38+
} else if (bundleLicense.startsWith("http")) {
39+
// Infer license from the URI
40+
val uri = try {
41+
URI(bundleLicense)
42+
} catch (e: URISyntaxException) {
43+
logger.info(
44+
"Invalid URI for license in Bundle-License value '{}' in {}",
45+
bundleLicense,
46+
context,
47+
e
48+
)
49+
return null
50+
}
51+
lookupLicenseByUri(uri)
52+
} else {
53+
// Infer license from "SPDX-Expression; licenseURI"
54+
// We ignore URI as the expression should be more important
55+
bundleLicense.substringBefore(";").let {
56+
try {
57+
licenseExpressionParser.parse(it)
58+
} catch (e: ParseException) {
59+
logger.info(
60+
"Unable to parse Bundle-License value '{}' in {}",
61+
bundleLicense,
62+
context,
63+
e
64+
)
65+
null
66+
}
67+
}
68+
}
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2019 Vladimir Sitnikov <[email protected]>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package com.github.vlsi.gradle.license
19+
20+
import com.github.vlsi.gradle.license.api.LicenseExpressionParser
21+
import com.github.vlsi.gradle.license.api.OsgiBundleLicenseParser
22+
import com.github.vlsi.gradle.license.api.SpdxLicense
23+
import com.github.vlsi.gradle.license.api.asExpression
24+
import org.junit.jupiter.api.Assertions.assertEquals
25+
import org.junit.jupiter.api.Assertions.assertNull
26+
import org.junit.jupiter.params.ParameterizedTest
27+
import org.junit.jupiter.params.provider.CsvSource
28+
29+
class OsgiBundleLicenseParserTest {
30+
@ParameterizedTest
31+
@CsvSource(
32+
"license from https uri^Apache-2.0^https://www.apache.org/licenses/LICENSE-2.0",
33+
"license from http uri^Apache-2.0^http://www.apache.org/licenses/LICENSE-2.0",
34+
"license from https uri^Apache-1.0^https://www.apache.org/licenses/LICENSE-1.0",
35+
"license from SPDX^Apache-2.0^Apache-2.0;https://www.apache.org/licenses/LICENSE-1.0",
36+
"SPDX expression^Apache-1.0 OR Apache-2.0^Apache-2.0 OR Apache-1.0;https://www.apache.org/licenses/LICENSE-1.0",
37+
delimiter = '^'
38+
)
39+
fun success(comment: String, expected: String, input: String) {
40+
val parser = OsgiBundleLicenseParser(LicenseExpressionParser()) {
41+
SpdxLicense.fromUriOrNull(it)?.asExpression()
42+
}
43+
assertEquals(expected, parser.parseOrNull(input, "test input").toString()) {
44+
"$comment, input: $input"
45+
}
46+
}
47+
48+
@ParameterizedTest
49+
@CsvSource(
50+
"unknown uri^https://www.apache.org/licenses/LICENSE-1.2",
51+
"invalid expression^Apache OR;http://www.apache.org/licenses/LICENSE-2.0",
52+
"multiple licenses^license1,license2",
53+
"multiple licenses^Apache-2.0;https://www.apache.org/licenses/LICENSE-2.0,Apache_1.0;https://www.apache.org/licenses/LICENSE-1.0",
54+
delimiter = '^'
55+
)
56+
fun fail(comment: String, input: String) {
57+
val parser = OsgiBundleLicenseParser(LicenseExpressionParser()) {
58+
SpdxLicense.fromUriOrNull(it)?.asExpression()
59+
}
60+
assertNull(parser.parseOrNull(input, "test input")) {
61+
"$comment should cause OsgiBundleLicenseParser.parse failure, input: $input"
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)