Skip to content

Commit

Permalink
adding similarity as a flag
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronpowell committed Jul 15, 2022
1 parent 35c2151 commit 3ab7a15
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 48 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog for `dotnet-delice`

## [1.8.0]

### Changed

- The similarity for the Sørensen–Dice coefficient comparison to SPDX templates is now an argument that defaults to 0.9, available via `--similarity`

## [1.7.1] - 2022-05-04

### Changed
Expand Down
7 changes: 5 additions & 2 deletions src/DotNetDelice.Licensing/LicenseBuilder.fs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ let private buildLicenseFromPackage
packagePath
(pId: LocalPackageInfo)
path
similarity
=
let licenseMetadata =
{ Type = None
Expand Down Expand Up @@ -123,7 +124,7 @@ let private buildLicenseFromPackage
| license when license.Type = LicenseType.File ->
match Path.Combine(path, packagePath, license.License.Replace('\\', Path.DirectorySeparatorChar))
|> File.ReadAllText
|> findMatchingLicense
|> (findMatchingLicense similarity)
with
| Some licenseSpdx ->
{ licenseMetadata with
Expand All @@ -149,13 +150,14 @@ let getPackageLicense
packageName
packageVersion
packageType
similarity
=
let identity =
PackageIdentity(packageName, packageVersion)

let checkLicenseContents' name url =
if checkLicenseContent then
checkLicenseContents name url
checkLicenseContents similarity name url
else
None

Expand All @@ -180,3 +182,4 @@ let getPackageLicense
.ToLower())
pId
path
similarity
8 changes: 4 additions & 4 deletions src/DotNetDelice.Licensing/LicenseCache.fs
Original file line number Diff line number Diff line change
Expand Up @@ -585,10 +585,10 @@ let private convertIfGitHub (licenseUrl: string) =
Regex.Replace(licenseUrl, "^https?:\/\/github\.com", "https://raw.githubusercontent.com").Replace("/blob/", "/")
else licenseUrl

let findMatchingLicense licenseContents =
descriptions |> Map.tryFindKey (fun _ licenseTemplate -> diceCoefficient licenseTemplate licenseContents > 0.9)
let findMatchingLicense similarity licenseContents =
descriptions |> Map.tryFindKey (fun _ licenseTemplate -> diceCoefficient licenseTemplate licenseContents > similarity)

let checkLicenseContents packageName licenseUrl =
let checkLicenseContents similarity packageName licenseUrl =
match dynamicLicenseCache.TryFind licenseUrl with
| Some lc -> Some lc
| None ->
Expand All @@ -597,7 +597,7 @@ let checkLicenseContents packageName licenseUrl =
else
try
let licenseContents = convertIfGitHub licenseUrl |> Http.RequestString
match findMatchingLicense licenseContents with
match findMatchingLicense similarity licenseContents with
| Some key ->
let lc =
{ Expression = key
Expand Down
128 changes: 86 additions & 42 deletions src/DotNetDelice/App.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module App

open McMaster.Extensions.CommandLineUtils
open System
open System.ComponentModel.DataAnnotations
open System.IO
open NuGet.ProjectModel
open NuGet.Common
Expand All @@ -11,7 +12,9 @@ open LicenseBuilder
open Spdx

let (|Proj|_|) arg =
let c str = String.Compare(str, arg, StringComparison.OrdinalIgnoreCase) = 0
let c str =
String.Compare(str, arg, StringComparison.OrdinalIgnoreCase) = 0

if c ".sln" then Some()
elif c ".csproj" then Some()
elif c ".fsproj" then Some()
Expand All @@ -28,80 +31,94 @@ let findProject path =
match Directory.GetFiles(path, "*.sln") with
| [| sln |] -> Path.GetFullPath sln
| [||] ->
match Directory.GetFiles(path, "*.csproj")
|> Array.append
<| Directory.GetFiles(path, "*.fsproj") with
match
Directory.GetFiles(path, "*.csproj")
|> Array.append
<| Directory.GetFiles(path, "*.fsproj")
with
| [| proj |] -> Path.GetFullPath proj
| [||] -> failwith "No project files found in the path"
| _ -> failwith "More than one project files found in the path"
| _ -> failwith "More than one solution file found in the path"

let getLicenses checkGitHub token checkLicenseContent (projectSpec: PackageSpec) =
let file = Path.Combine(projectSpec.RestoreMetadata.OutputPath, "project.assets.json")
let getLicenses checkGitHub token checkLicenseContent similarity (projectSpec: PackageSpec) =
let file =
Path.Combine(projectSpec.RestoreMetadata.OutputPath, "project.assets.json")

let lockFile = LockFileUtilities.GetLockFile(file, NullLogger.Instance)

lockFile.Libraries
|> Seq.map
(fun lib ->
getPackageLicense projectSpec checkGitHub token checkLicenseContent lib.Name lib.Version lib.Type)
|> Seq.map (fun lib ->
getPackageLicense projectSpec checkGitHub token checkLicenseContent lib.Name lib.Version lib.Type similarity)

let getLicensesForTool checkGitHub token checkLicenseContent (projectSpec: PackageSpec) =
let getLicensesForTool checkGitHub token checkLicenseContent (projectSpec: PackageSpec) similarity =
let package =
projectSpec.TargetFrameworks
|> Seq.map (fun fx ->
let dep = fx.Dependencies |> Seq.head

let pkg =
NuGet.Protocol.LocalFolderUtility.GetPackageV3
(projectSpec.RestoreMetadata.PackagesPath, dep.Name, dep.LibraryRange.VersionRange.MinVersion,
MemoryLogger.Instance)
NuGet.Protocol.LocalFolderUtility.GetPackageV3(
projectSpec.RestoreMetadata.PackagesPath,
dep.Name,
dep.LibraryRange.VersionRange.MinVersion,
MemoryLogger.Instance
)

pkg)
|> Seq.head

let depGroups = package.Nuspec.GetDependencyGroups()

depGroups
|> Seq.collect (fun dg -> dg.Packages)
|> Seq.map
(fun p ->
getPackageLicense projectSpec checkGitHub token checkLicenseContent p.Id p.VersionRange.MinVersion
"package")
|> Seq.map (fun p ->
getPackageLicense
projectSpec
checkGitHub
token
checkLicenseContent
p.Id
p.VersionRange.MinVersion
"package"
similarity)

[<HelpOption>]
type Cli() =

[<Argument(0,
Description =
"The path to a .sln, .csproj or .fsproj file, or to a directory containing a .NET Core solution/project. If none is specified, the current directory will be used.")>]
Description = "The path to a .sln, .csproj or .fsproj file, or to a directory containing a .NET Core solution/project. If none is specified, the current directory will be used.")>]
member val Path = "" with get, set

[<OptionAttribute(Description = "Output result as JSON")>]
[<Option(Description = "Output result as JSON")>]
member val Json = false with get, set

[<OptionAttribute("--json-output", Description = "Path to JSON file rather than stdout")>]
[<Option("--json-output", Description = "Path to JSON file rather than stdout")>]
member val JsonOutput = "" with get, set

[<OptionAttribute("--check-github",
Description =
"If provided delice will attempt to look up the license via the GitHub API (provided it's GitHub hosted)")>]
[<Option("--check-github",
Description = "If provided delice will attempt to look up the license via the GitHub API (provided it's GitHub hosted)")>]
member val CheckGitHub = false with get, set

[<OptionAttribute("--github-token",
Description =
"A GitHub Personal Access Token to perform authenticated requests against the API (used with --check-github). This ensures the tool isn't rate-limited when running")>]
[<Option("--github-token",
Description = "A GitHub Personal Access Token to perform authenticated requests against the API (used with --check-github). This ensures the tool isn't rate-limited when running")>]
member val GitHubToken = "" with get, set

[<OptionAttribute("--check-license-content",
Description =
"When set delice will attempt to look at the license text and match it against some known license templates")>]
[<Option("--check-license-content",
Description = "When set delice will attempt to look at the license text and match it against some known license templates")>]
member val CheckLicenseContent = false with get, set

[<OptionAttribute("--refresh-spdx", Description = "Refreshes the SPDX license.json file used by the tool")>]
[<Option("--refresh-spdx", Description = "Refreshes the SPDX license.json file used by the tool")>]
member val RefreshSpdx = false with get, set

[<Option("--similarity",
Description = "The level of similarity to apply when comparing license contents to the SPDX templates")>]
[<Range(0, 1)>]
member val Similarity = 0.9 with get, set

member this.OnExecute() =
if this.RefreshSpdx then
getSpdx true
|> Async.RunSynchronously
|> ignore
getSpdx true |> Async.RunSynchronously |> ignore

let path =
match this.Path with
Expand All @@ -118,47 +135,74 @@ type Cli() =
| None ->
printfn "Failed to generate the dependency graph for '%s'." path
printfn "Ensure that the project has been restored and compiled before running delice."

printfn
"delice only supports SDK project files (.NET Core, NETStandard, etc.), not legacy MSBuild ones (common for .NET Framework)."
| Some dependencyGraph ->
let getLicenses' = getLicenses this.CheckGitHub this.GitHubToken this.CheckLicenseContent
let getLicenses' =
getLicenses this.CheckGitHub this.GitHubToken this.CheckLicenseContent this.Similarity

let projects =
dependencyGraph.Projects
|> Seq.filter (fun projectSpec ->
projectSpec.RestoreMetadata.ProjectStyle <> ProjectStyle.Unknown
&& projectSpec.RestoreMetadata.ProjectStyle <> ProjectStyle.DotnetCliTool)
projectSpec.RestoreMetadata.ProjectStyle
<> ProjectStyle.Unknown
&& projectSpec.RestoreMetadata.ProjectStyle
<> ProjectStyle.DotnetCliTool)

if this.Json then
let toolLicenseResults =
dependencyGraph.Projects
|> Seq.filter (fun ps -> ps.RestoreMetadata.ProjectStyle = ProjectStyle.DotnetCliTool)
|> Seq.map (fun projectSpec ->
getLicensesForTool this.CheckGitHub this.GitHubToken this.CheckLicenseContent projectSpec
getLicensesForTool
this.CheckGitHub
this.GitHubToken
this.CheckLicenseContent
projectSpec
this.Similarity
|> jsonBuilder projectSpec.Name)

projects
|> Seq.map (fun projectSpec -> getLicenses' projectSpec |> jsonBuilder projectSpec.Name)
|> Seq.map (fun projectSpec ->
getLicenses' projectSpec
|> jsonBuilder projectSpec.Name)
|> Seq.append toolLicenseResults
|> jsonPrinter this.JsonOutput
else
projects
|> Seq.iter (fun projectSpec ->
getLicenses' projectSpec |> prettyPrint projectSpec.Name
getLicenses' projectSpec
|> prettyPrint projectSpec.Name

printfn "")

dependencyGraph.Projects
|> Seq.filter (fun ps -> ps.RestoreMetadata.ProjectStyle = ProjectStyle.DotnetCliTool)
|> Seq.iter (fun projectSpec ->
getLicensesForTool this.CheckGitHub this.GitHubToken this.CheckLicenseContent projectSpec
getLicensesForTool
this.CheckGitHub
this.GitHubToken
this.CheckLicenseContent
projectSpec
this.Similarity
|> prettyPrint projectSpec.Name

printfn "")

let unknownProjectStyles =
dependencyGraph.Projects
|> Seq.filter (fun projectSpec -> projectSpec.RestoreMetadata.ProjectStyle = ProjectStyle.Unknown)

if Seq.length unknownProjectStyles > 1 then
printfn "The following projects were skipped as they are of an unsupported project style:"

unknownProjectStyles
|> Seq.iteri (fun i projectSpec ->
let prefix =
if i = Seq.length unknownProjectStyles - 1 then "" else ""
if i = Seq.length unknownProjectStyles - 1 then
""
else
""

printfn "%s── %s" prefix projectSpec.Name)
1 change: 1 addition & 0 deletions src/DotNetDelice/DotNetDelice.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BlackFox.ColoredPrintf" Version="1.0.3" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotNetDelice.Licensing\DotNetDelice.Licensing.fsproj" />
Expand Down

0 comments on commit 3ab7a15

Please sign in to comment.