Skip to content

Miscellaneous improvements to DotNet / NuGet code #1482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 26, 2019
2 changes: 1 addition & 1 deletion analyzer/src/funTest/kotlin/DotNetTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class DotNetTest : StringSpec() {

"Definition File is correctly mapped" {
val mapper = XmlMapper().registerKotlinModule()
val result = mapper.readValue<List<DotNet.Companion.ItemGroup>>(packageFile)
val result = mapper.readValue<List<DotNet.ItemGroup>>(packageFile)

result shouldNotBe null
result.size shouldBe 4
Expand Down
2 changes: 1 addition & 1 deletion analyzer/src/funTest/kotlin/NuGetTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class NuGetTest : StringSpec() {

"Definition File is correctly mapped" {
val mapper = XmlMapper().registerKotlinModule()
val result: NuGet.Companion.Packages = mapper.readValue(packageFile)
val result = mapper.readValue<NuGet.PackagesConfig>(packageFile)

result shouldNotBe null
result.packages shouldNotBe null
Expand Down
85 changes: 42 additions & 43 deletions analyzer/src/main/kotlin/DotNetSupport.kt
Original file line number Diff line number Diff line change
Expand Up @@ -74,30 +74,31 @@ class DotNetSupport(
path = extractRepositoryPath(node)
)

private fun extractPackageId(node: JsonNode) = Identifier(
type = PROVIDER_NAME,
namespace = "",
name = node["id"]?.textValue() ?: "",
version = node["version"]?.textValue() ?: ""
)

private fun extractPackageId(node: JsonNode) =
Identifier(
type = PROVIDER_NAME,
namespace = "",
name = node["id"]?.textValue() ?: "",
version = node["version"]?.textValue() ?: ""
)

private fun extractDeclaredLicenses(node: JsonNode) = sortedSetOf<String>().apply {
val license = node["license"]?.textValue() ?: node["licenseUrl"]?.textValue() ?: ""
// most nuget packages only provide a licenseUrl,
// which counts as declared license, will be changed when scanner runs
if (license.isNotEmpty()) {
add(license)
private fun extractDeclaredLicenses(node: JsonNode) =
sortedSetOf<String>().apply {
val license = node["license"]?.textValue() ?: node["licenseUrl"]?.textValue() ?: ""
// Most nuget packages only provide a "licenseUrl" which counts as a declared license.
if (license.isNotEmpty()) {
add(license)
}
}
}

private fun extractRemoteArtifact(node: JsonNode, nupkgUrl: String) = RemoteArtifact(
url = nupkgUrl,
hash = node["packageHash"]?.textValue() ?: "",
hashAlgorithm = HashAlgorithm.fromString(
node["packageHashAlgorithm"]?.textValue() ?: ""
private fun extractRemoteArtifact(node: JsonNode, nupkgUrl: String) =
RemoteArtifact(
url = nupkgUrl,
hash = node["packageHash"]?.textValue() ?: "",
hashAlgorithm = HashAlgorithm.fromString(
node["packageHashAlgorithm"]?.textValue() ?: ""
)
)
)

private fun getCatalogURL(registrationNode: JsonNode): String =
registrationNode["catalogEntry"]?.textValue() ?: ""
Expand All @@ -112,13 +113,13 @@ class DotNetSupport(
.replace(")", "")
return rangeReplaces.split(",").elementAt(0)
}

}

var packages = mutableMapOf<String, Package>()
var errors = mutableListOf<OrtIssue>()
var scope = Scope(SCOPE_NAME, sortedSetOf())
// consists of map key: id, version and pair map entry: nupkg url as string and catalogentry as jsonnode
val packages = mutableListOf<Package>()
val errors = mutableListOf<OrtIssue>()
val scope = Scope(SCOPE_NAME, sortedSetOf())

// Maps an (id, version) pair to a (nupkg URL, catalog entry) pair.
private val packageReferencesAlreadyFound = mutableMapOf<Pair<String, String>, Pair<String, JsonNode>>()

init {
Expand All @@ -131,14 +132,13 @@ class DotNetSupport(
val pkg = packageReferenceToPackage(packageReference)

if (pkg != Package.EMPTY) {
packages["${pkg.id.name}:${pkg.id.version}"] = pkg
packages += pkg
}
}
}

private fun packageReferenceToPackage(packageReference: PackageReference): Package {
return jsonNodeToPackage(getPackageReferenceJsonContent(packageReference))
}
private fun packageReferenceToPackage(packageReference: PackageReference) =
jsonNodeToPackage(getPackageReferenceJsonContent(packageReference))

private fun getPackageReferenceJsonContent(packageReference: PackageReference): Pair<String, JsonNode> {
if (packageReferencesAlreadyFound.containsKey(
Expand Down Expand Up @@ -256,9 +256,8 @@ class DotNetSupport(
return packageJsonNode
}

private fun hasPackageReferenceAlready(nodeAsPair: Pair<String, String>): Boolean {
return packageReferencesAlreadyFound.containsKey(nodeAsPair)
}
private fun hasPackageReferenceAlready(nodeAsPair: Pair<String, String>) =
packageReferencesAlreadyFound.containsKey(nodeAsPair)

private fun getInformationURL(packageID: String, version: String): Pair<String, String>? {
val registrationInfo: String? = try {
Expand All @@ -282,12 +281,12 @@ class DotNetSupport(
}
}

private fun getIdUrl(packageID: String, version: String): String {
val node = getCreateSearchRestAPIURL(packageID)

return getRightVersionUrl(node["data"]?.elements(), packageID, version)
?: getFirstMatchingIdUrl(node["data"]?.elements(), packageID) ?: ""
}
private fun getIdUrl(packageID: String, version: String) =
getCreateSearchRestAPIURL(packageID).let { node ->
getRightVersionUrl(node["data"]?.elements(), packageID, version)
?: getFirstMatchingIdUrl(node["data"]?.elements(), packageID)
?: ""
}

private fun getRightVersionUrl(
dataIterator: Iterator<JsonNode>?,
Expand All @@ -297,10 +296,11 @@ class DotNetSupport(
val packageNode = dataIterator.next()
if (packageNode["id"].textValueOrEmpty() == packageID) {
packageNode["versions"].elements().forEach {
if (it["version"].textValueOrEmpty() == version)
if (it["version"].textValueOrEmpty() == version) {
return it["@id"].textValueOrEmpty()
else if (!dataIterator.hasNext() && version == "latest")
} else if (!dataIterator.hasNext() && version == "latest") {
return it["@id"].textValueOrEmpty()
}
}
}
}
Expand All @@ -319,11 +319,10 @@ class DotNetSupport(
return null
}

private fun getCreateSearchRestAPIURL(packageID: String): JsonNode {
return jacksonObjectMapper().readTree(
private fun getCreateSearchRestAPIURL(packageID: String) =
jacksonObjectMapper().readTree(
"https://api-v2v3search-0.nuget.org/query?q=\"$packageID\"&prerelease=false".requestFromNugetAPI()
)
}

private fun String.requestFromNugetAPI(): String {
if (isNullOrEmpty()) {
Expand Down
45 changes: 22 additions & 23 deletions analyzer/src/main/kotlin/managers/DotNet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,38 +51,21 @@ class DotNet(
repoConfig: RepositoryConfiguration
) : PackageManager(name, analyzerRoot, analyzerConfig, repoConfig) {
companion object {
fun mapPackageReferences(workingDir: File): Map<String, String> {
fun mapPackageReferences(definitionFile: File): Map<String, String> {
val map = mutableMapOf<String, String>()
val mapper = XmlMapper().registerKotlinModule()
val mappedFile: List<ItemGroup> = mapper.readValue(workingDir)
val itemGroups = mapper.readValue<List<ItemGroup>>(definitionFile)

mappedFile.forEach { itemGroup ->
itemGroups.forEach { itemGroup ->
itemGroup.packageReference?.forEach {
if (!it.Include.isNullOrEmpty()) {
map[it.Include] = it.Version ?: " "
if (!it.include.isNullOrEmpty()) {
map[it.include] = it.version ?: " "
}
}
}

return map
}

@JsonIgnoreProperties(ignoreUnknown = true)
data class ItemGroup(
@JsonProperty(value = "PackageReference")
@JacksonXmlElementWrapper(useWrapping = false)
val packageReference: List<PackageReference>?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class PackageReference(
@JsonProperty(value = "Include")
@JacksonXmlProperty(isAttribute = true)
val Include: String?,
@JsonProperty(value = "Version")
@JacksonXmlProperty(isAttribute = true)
val Version: String?
)
}

class Factory : AbstractPackageManagerFactory<DotNet>("DotNet") {
Expand All @@ -96,6 +79,22 @@ class DotNet(
DotNet(managerName, analyzerRoot, analyzerConfig, repoConfig)
}

// See https://docs.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files.
@JsonIgnoreProperties(ignoreUnknown = true)
data class ItemGroup(
@JsonProperty(value = "PackageReference")
@JacksonXmlElementWrapper(useWrapping = false)
val packageReference: List<PackageReference>?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class PackageReference(
@JacksonXmlProperty(isAttribute = true, localName = "Include")
val include: String?,
@JacksonXmlProperty(isAttribute = true, localName = "Version")
val version: String?
)

override fun resolveDependencies(definitionFile: File): ProjectAnalyzerResult? {
val workingDir = definitionFile.parentFile
val dotnet = DotNetSupport(mapPackageReferences(definitionFile), workingDir)
Expand All @@ -117,7 +116,7 @@ class DotNet(

return ProjectAnalyzerResult(
project,
packages = dotnet.packages.values.map { it.toCuratedPackage() }.toSortedSet(),
packages = dotnet.packages.map { it.toCuratedPackage() }.toSortedSet(),
errors = dotnet.errors
)
}
Expand Down
39 changes: 20 additions & 19 deletions analyzer/src/main/kotlin/managers/NuGet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,32 +51,17 @@ class NuGet(
repoConfig: RepositoryConfiguration
) : PackageManager(name, analyzerRoot, analyzerConfig, repoConfig) {
companion object {
fun mapPackageReferences(workingDir: File): Map<String, String> {
fun mapPackageReferences(definitionFile: File): Map<String, String> {
val map = mutableMapOf<String, String>()
val mapper = XmlMapper().registerKotlinModule()
val mappedFile: Packages = mapper.readValue(workingDir)
val packagesConfig = mapper.readValue<PackagesConfig>(definitionFile)

mappedFile.packages?.forEach {
packagesConfig.packages?.forEach {
map[it.id] = it.version
}

return map
}

@JsonIgnoreProperties(ignoreUnknown = true)
data class Packages(
@JsonProperty(value = "package")
@JacksonXmlElementWrapper(useWrapping = false)
val packages: List<Package>?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Package(
@JacksonXmlProperty(isAttribute = true)
val id: String,
@JacksonXmlProperty(isAttribute = true)
val version: String
)
}

class Factory : AbstractPackageManagerFactory<NuGet>("NuGet") {
Expand All @@ -90,6 +75,22 @@ class NuGet(
NuGet(managerName, analyzerRoot, analyzerConfig, repoConfig)
}

// See https://docs.microsoft.com/en-us/nuget/reference/packages-config.
@JsonIgnoreProperties(ignoreUnknown = true)
data class PackagesConfig(
@JsonProperty(value = "package")
@JacksonXmlElementWrapper(useWrapping = false)
val packages: List<Package>?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Package(
@JacksonXmlProperty(isAttribute = true)
val id: String,
@JacksonXmlProperty(isAttribute = true)
val version: String
)

override fun resolveDependencies(definitionFile: File): ProjectAnalyzerResult? {
val workingDir = definitionFile.parentFile
val nuget = DotNetSupport(mapPackageReferences(definitionFile), workingDir)
Expand All @@ -111,7 +112,7 @@ class NuGet(

return ProjectAnalyzerResult(
project = project,
packages = nuget.packages.values.map { it.toCuratedPackage() }.toSortedSet(),
packages = nuget.packages.map { it.toCuratedPackage() }.toSortedSet(),
errors = nuget.errors
)
}
Expand Down
3 changes: 3 additions & 0 deletions model/src/main/kotlin/OutputFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ enum class OutputFormat(val mapper: ObjectMapper, val fileExtension: String, var
*/
YAML(yamlMapper, "yml", "yaml");

/**
* The list of file extensions used by this output format.
*/
val fileExtensions = listOf(fileExtension, *aliases)
}

Expand Down