Skip to content
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

Adding support for pub package manager #888

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Component Detection supports detecting libraries from the following ecosystem:
| [Poetry (Python, lockfiles only)](docs/detectors/poetry.md) | ✔ | ❌ |
| Ruby | ✔ | ✔ |
| Rust | ✔ | ✔ |
| Pub | ✔ | ❌ |

For a complete feature overview refer to [feature-overview.md](docs/feature-overview.md)

Expand Down
8 changes: 5 additions & 3 deletions docs/schema/manifest.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@
"Conda",
"Spdx",
"Vcpkg",
"DockerReference"
"DockerReference",
"Pub"
]
}
}
Expand Down Expand Up @@ -343,7 +344,8 @@
"Conda",
"Spdx",
"Vcpkg",
"DockerReference"
"DockerReference",
"Pub"
]
},
"id": {
Expand Down Expand Up @@ -417,4 +419,4 @@
"resultCode",
"sourceDirectory"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class TypedComponentConverter : JsonConverter
{ ComponentType.DockerReference, typeof(DockerReferenceComponent) },
{ ComponentType.Vcpkg, typeof(VcpkgComponent) },
{ ComponentType.Spdx, typeof(SpdxComponent) },
{ ComponentType.Pub, typeof(PubComponent) },
};

public override bool CanWrite
Expand Down
3 changes: 3 additions & 0 deletions src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,7 @@ public enum DetectorClass

/// <summary>Indicates a detector applies to Docker references.</summary>
DockerReference,

/// <summary>Indicates a detector applies to Pub references.</summary>
Pub,
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,7 @@ public enum ComponentType : byte

[EnumMember]
Conan = 17,

[EnumMember]
Pub = 18,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace Microsoft.ComponentDetection.Contracts.TypedComponent;

public class PubComponent : TypedComponent
{
public PubComponent()
{
/* Reserved for deserialization */
}

public PubComponent(string name, string version, string dependency, string hash = null, string url = null)
{
this.Name = this.ValidateRequiredInput(name, nameof(this.Name), nameof(ComponentType.Pub));
this.Version = this.ValidateRequiredInput(version, nameof(this.Version), nameof(ComponentType.Pub));
this.Dependency = dependency;
this.Hash = hash; // Not required;
this.Url = url;
}

public string Name { get; }

public string Version { get; }

public string Hash { get; }

public string Url { get; }

public string Dependency { get; }

public override ComponentType Type => ComponentType.Pub;

public override string Id => $"{this.Name} {this.Version} - {this.Type}";

public override string ToString()
{
return $"Name={this.Name}\tVersion={this.Version}\tUrl={this.Url}";
}

protected bool Equals(PubComponent other) => this.Name == other.Name && this.Version == other.Version && this.Hash == other.Hash && this.Url == other.Url && this.Dependency == other.Dependency;

public override bool Equals(object obj)
{
if (obj is null)
{
return false;
}

if (ReferenceEquals(this, obj))
{
return true;
}

if (obj.GetType() != this.GetType())
{
return false;
}

return this.Equals((PubComponent)obj);
}

public override int GetHashCode() => this.Name.GetHashCode() ^ this.Version.GetHashCode() ^ this.Hash.GetHashCode() ^ this.Url.GetHashCode() ^ this.Dependency.GetHashCode();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
namespace Microsoft.ComponentDetection.Detectors.Pub;

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.Extensions.Logging;
using YamlDotNet.Serialization;

public class PubComponentDetector : FileComponentDetector, IDefaultOffComponentDetector
{
public PubComponentDetector(
IComponentStreamEnumerableFactory componentStreamEnumerableFactory,
IObservableDirectoryWalkerFactory walkerFactory,
ILogger<PubComponentDetector> logger)
{
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory;
this.Scanner = walkerFactory;
this.Logger = logger;
}

public override string Id => "pub";

public override IEnumerable<string> Categories => new[] { Enum.GetName(typeof(DetectorClass), DetectorClass.Pub) };

public override IEnumerable<ComponentType> SupportedComponentTypes { get; } = new[] { ComponentType.Pub };

public override int Version => 1;

public override IList<string> SearchPatterns => new List<string> { "pubspec.lock" };

protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs)
{
try
{
using var reader = new StreamReader(processRequest.ComponentStream.Stream);
var text = await reader.ReadToEndAsync();

var deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().Build();
var parsedFile = deserializer.Deserialize<PubSpecLock>(text);
this.Logger.LogDebug("SDK {Dart}", parsedFile.Sdks.Dart);

foreach (var package in parsedFile.Packages)
{
if (package.Value.Source == "hosted")
{
var component = new PubComponent(
package.Value.GetName(),
package.Value.Version,
package.Value.Dependency,
package.Value.GetSha256(),
package.Value.GePackageDownloadedSource());
this.Logger.LogInformation("Registering component {Package}", component);

processRequest.SingleFileComponentRecorder.RegisterUsage(new DetectedComponent(component));
}
}
}
catch (Exception ex)
{
this.Logger.LogError(ex, "Error while parsing lock file");
}
}
}
18 changes: 18 additions & 0 deletions src/Microsoft.ComponentDetection.Detectors/pub/PubSpecLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Microsoft.ComponentDetection.Detectors.Pub;

using System.Collections.Generic;
using System.Runtime.Serialization;
using YamlDotNet.Serialization;

/// <summary>
/// Model of the pub-spec lock file.
/// </summary>
[DataContract]
public class PubSpecLock
{
[YamlMember(Alias = "packages")]
public Dictionary<string, PubSpecLockPackage> Packages { get; set; }

[YamlMember(Alias = "sdks")]
public SDK Sdks { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace Microsoft.ComponentDetection.Detectors.Pub;

using System.Runtime.Serialization;
using YamlDotNet.Serialization;

/// <summary>
/// Model of the pub-spec lock file.
/// </summary>
[DataContract]
public class PubSpecLockPackage
{
[YamlMember(Alias = "source")]
public string Source { get; set; }

[YamlMember(Alias = "version")]
public string Version { get; set; }

[YamlMember(Alias = "dependency")]
public string Dependency { get; set; }

[YamlMember(Alias = "description")]
public dynamic Description { get; set; }

/// <summary>
/// /// Returns the description\sha256 path
/// The value can be null.
/// </summary>
/// <returns> Returns the package SHA-256 as in the pubspec.lock file.</returns>
public string GetSha256() => this.Description["sha256"];

/// <summary>
/// Returns the description\url path
/// The value can be null.
/// </summary>
/// <returns>Returns the package url as in the pubspec.lock file.</returns>
public string GePackageDownloadedSource() => this.Description["url"];

/// <summary>
/// Returns the description\name path
/// The value can be null.
/// </summary>
/// <returns>Returns the package name as in the pubspec.lock file.</returns>
public string GetName() => this.Description["name"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Microsoft.ComponentDetection.Detectors.Pub;

using System.Runtime.Serialization;
using YamlDotNet.Serialization;

/// <summary>
/// Model of the pub-spec lock file.
/// </summary>
[DataContract]
public class PubSpecLockPackageDescription
{
[YamlMember(Alias = "name")]
public string Name { get; set; }

[YamlMember(Alias = "sha256")]
public string Sha256 { get; set; }

[YamlMember(Alias = "url")]
public string Url { get; set; }
}
14 changes: 14 additions & 0 deletions src/Microsoft.ComponentDetection.Detectors/pub/SDK.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Microsoft.ComponentDetection.Detectors.Pub;

using System.Runtime.Serialization;
using YamlDotNet.Serialization;

[DataContract]
public class SDK
{
[YamlMember(Alias = "dart")]
public string Dart { get; set; }

[YamlMember(Alias = "flutter")]
public string Flutter { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Microsoft.ComponentDetection.Orchestrator.Extensions;
using Microsoft.ComponentDetection.Detectors.Pip;
using Microsoft.ComponentDetection.Detectors.Pnpm;
using Microsoft.ComponentDetection.Detectors.Poetry;
using Microsoft.ComponentDetection.Detectors.Pub;
using Microsoft.ComponentDetection.Detectors.Ruby;
using Microsoft.ComponentDetection.Detectors.Rust;
using Microsoft.ComponentDetection.Detectors.Spdx;
Expand Down Expand Up @@ -139,6 +140,8 @@ public static IServiceCollection AddComponentDetection(this IServiceCollection s
services.AddSingleton<IYarnLockFileFactory, YarnLockFileFactory>();
services.AddSingleton<IComponentDetector, YarnLockComponentDetector>();

// Pub
services.AddSingleton<IComponentDetector, PubComponentDetector>();
return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
namespace Microsoft.ComponentDetection.Detectors.Tests;

using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.ComponentDetection.Detectors.Pub;
using Microsoft.ComponentDetection.TestsUtilities;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
[TestCategory("Governance/All")]
[TestCategory("Governance/ComponentDetection")]
public class PubComponentDetectorTests : BaseDetectorTest<PubComponentDetector>
{
private readonly string testPubSpecLockFile = @"# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
analyzer:
dependency: ""direct dev""
description:
name: analyzer
sha256: ""sh1""
url: ""https://pub.dev""
source: hosted
version: ""5.13.0""
archive:
dependency: transitive
description:
name: archive
sha256: ""sh2""
url: ""https://pub.dev""
source: hosted
version: ""3.4.4""
async:
dependency: direct main
description:
name: async
sha256: ""sh3""
url: ""https://pub.dev""
source: hosted
version: ""2.11.0""
flutter:
dependency: ""direct main""
description: flutter
source: sdk
version: ""0.0.0""
sdks:
dart: "">=3.0.0 <4.0.0""
flutter: "">=3.10.0""
";

[TestMethod]
public async Task TestDetectorAsync()
{
var (result, componentRecorder) = await this.DetectorTestUtility
.WithFile("pubspec.lock", this.testPubSpecLockFile)
.ExecuteDetectorAsync();

result.ResultCode.Should().Be(ProcessingResultCode.Success);
componentRecorder.GetDetectedComponents().Count().Should().Be(3);

var components = componentRecorder.GetDetectedComponents().Select(x => x.Component).ToArray();

components.Should().Contain(new PubComponent(
"analyzer",
"5.13.0",
"direct dev",
"sh1",
"https://pub.dev"));

components.Should().Contain(new PubComponent(
"archive",
"3.4.4",
"transitive",
"sh2",
"https://pub.dev"));

components.Should().Contain(new PubComponent(
"async",
"2.11.0",
"direct main",
"sh3",
"https://pub.dev"));
}
}
Loading