Skip to content

Commit

Permalink
adding support for pub package manager
Browse files Browse the repository at this point in the history
  • Loading branch information
Aviad authored and Aviad committed Nov 3, 2023
1 parent 62c482b commit 93b7e1c
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 0 deletions.
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,72 @@
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; set; }

public string Hash { get; set; }

public string Url { get; set; }

public string Dependency { get; set; }

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()
{
unchecked
{
var hashCode = this.Name != null ? this.Name.GetHashCode() : 0;
hashCode = (hashCode * 397) ^ (this.Version != null ? this.Version.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.Hash != null ? this.Hash.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.Url != null ? this.Url.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.Dependency != null ? this.Dependency.GetHashCode() : 0);
return hashCode;
}
}
}
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
{
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)
{
using var reader = new StreamReader(processRequest.ComponentStream.Stream);
var text = await reader.ReadToEndAsync();

var deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().Build();
try
{
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"));
}
}

0 comments on commit 93b7e1c

Please sign in to comment.