From a5c7a5dd66acdfbfd8cc87b479dbba89515b811d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Kr=C3=A4mer?= Date: Thu, 1 Dec 2022 09:06:34 +0100 Subject: [PATCH 01/25] Update README.md Update help output Signed-off-by: Kraemer, Benjamin --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 81297a01..48eab0e2 100755 --- a/README.md +++ b/README.md @@ -14,12 +14,12 @@ The CycloneDX module for .NET creates a valid CycloneDX bill-of-material documen This module runs on * .NET Core 3.1 -* .NET 6.0. +* .NET 6.0 This module no longer runs on * .NET Core 2.1 -* .NET5 +* .NET 5 * see https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core for more infomation ## Usage @@ -56,10 +56,12 @@ docker run cyclonedx/cyclonedx-dotnet [OPTIONS] Usage: dotnet CycloneDX [options] Arguments: - path The path to a .sln, .csproj, .fsproj, .vbproj, or packages.config file or the path to a directory which will be recursively analyzed for packages.config files + path The path to a .sln, .csproj, .fsproj, .vbproj, or packages.config file or the path to a directory which will be recursively analyzed for packages.config files Options: -v|--version Output the tool version and exit + -tfm|--framework The target framework to use. If not defined, all will be aggregated. + -rt|--runtime The runtime to use. If not defined, all will be aggregated. -o|--out The directory to write the BOM -f|--filename Optionally provide a filename for the BOM (default: bom.xml or bom.json) -j|--json Produce a JSON BOM instead of XML @@ -86,7 +88,7 @@ Options: -st|--set-type Override the default BOM metadata component type (defaults to application). Allowed values are: Null, Application, Framework, Library, OperationSystem, Device, File, Container, Firmware. Default value is: Null. - -?|-h|--help Show help information. + -?|-h|--help Show help information. ``` #### Examples From 98c02fc82f6217d75849b05261b768be01bd98e7 Mon Sep 17 00:00:00 2001 From: Chris Blyth Date: Thu, 15 Dec 2022 16:25:23 +0000 Subject: [PATCH 02/25] Fix bug when nuget package has a multiple condition licence expression Signed-off-by: Chris Blyth --- CycloneDX.Tests/NugetV3ServiceTests.cs | 59 ++++++++++++++++++++++++++ CycloneDX/Services/NugetV3Service.cs | 12 +++--- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/CycloneDX.Tests/NugetV3ServiceTests.cs b/CycloneDX.Tests/NugetV3ServiceTests.cs index 9ead85b6..ac78edb4 100644 --- a/CycloneDX.Tests/NugetV3ServiceTests.cs +++ b/CycloneDX.Tests/NugetV3ServiceTests.cs @@ -366,5 +366,64 @@ public async Task GetComponent_GitHubLicenseLookup_FromRepository_WhenLicenseInv Assert.Single(component.Licenses); Assert.Equal("LicenseId", component.Licenses.First().License.Id); } + + [Fact] + public async Task GetComponent_SingleLicenseExpression_ReturnsComponent() + { + var nuspecFileContents = @" + + + testpackage + Apache-2.0 + + "; + var mockFileSystem = new MockFileSystem(new Dictionary + { + { XFS.Path(@"c:\nugetcache\testpackage\1.0.0\testpackage.nuspec"), new MockFileData(nuspecFileContents) }, + }); + + var mockGitHubService = new Mock(); + + var nugetService = new NugetV3Service(null, + mockFileSystem, + new List { XFS.Path(@"c:\nugetcache") }, + mockGitHubService.Object, + new NullLogger(), false); + + var component = await nugetService.GetComponentAsync("testpackage", "1.0.0", Component.ComponentScope.Required).ConfigureAwait(false); + + Assert.Single(component.Licenses); + Assert.Equal("Apache-2.0", component.Licenses.First().License.Id); + } + + [Fact] + public async Task GetComponent_MultiLicenseExpression_ReturnsComponent() + { + var nuspecFileContents = @" + + + testpackage + Apache-2.0 OR MPL-2.0 + + "; + var mockFileSystem = new MockFileSystem(new Dictionary + { + { XFS.Path(@"c:\nugetcache\testpackage\1.0.0\testpackage.nuspec"), new MockFileData(nuspecFileContents) }, + }); + + var mockGitHubService = new Mock(); + + var nugetService = new NugetV3Service(null, + mockFileSystem, + new List { XFS.Path(@"c:\nugetcache") }, + mockGitHubService.Object, + new NullLogger(), false); + + var component = await nugetService.GetComponentAsync("testpackage", "1.0.0", Component.ComponentScope.Required).ConfigureAwait(false); + + Assert.Equal(2, component.Licenses.Count); + Assert.Contains(component.Licenses, choice => choice.License.Id.Equals("Apache-2.0")); + Assert.Contains(component.Licenses, choice => choice.License.Id.Equals("MPL-2.0")); + } } } diff --git a/CycloneDX/Services/NugetV3Service.cs b/CycloneDX/Services/NugetV3Service.cs index 11d0fda5..aaa79d1c 100644 --- a/CycloneDX/Services/NugetV3Service.cs +++ b/CycloneDX/Services/NugetV3Service.cs @@ -170,10 +170,11 @@ public async Task GetComponentAsync(string name, string version, Comp var licenseMetadata = nuspecModel.nuspecReader.GetLicenseMetadata(); if (licenseMetadata != null && licenseMetadata.Type == LicenseType.Expression) { - Action licenseProcessor = delegate(NuGetLicense nugetLicense) + Action licenseProcessor = delegate (NuGetLicense nugetLicense) { var license = new License { Id = nugetLicense.Identifier, Name = nugetLicense.Identifier }; - component.Licenses = new List { new LicenseChoice { License = license } }; + component.Licenses ??= new List(); + component.Licenses.Add(new LicenseChoice { License = license }); }; licenseMetadata.LicenseExpression.OnEachLeafNode(licenseProcessor, null); } @@ -231,7 +232,8 @@ public async Task GetComponentAsync(string name, string version, Comp { var externalReference = new ExternalReference { - Type = ExternalReference.ExternalReferenceType.Website, Url = projectUrl + Type = ExternalReference.ExternalReferenceType.Website, + Url = projectUrl }; component.ExternalReferences = new List { externalReference }; } @@ -243,7 +245,8 @@ public async Task GetComponentAsync(string name, string version, Comp { var externalReference = new ExternalReference { - Type = ExternalReference.ExternalReferenceType.Vcs, Url = vcsUrl + Type = ExternalReference.ExternalReferenceType.Vcs, + Url = vcsUrl }; if (null == component.ExternalReferences) { @@ -301,7 +304,6 @@ await resource.CopyNupkgToStreamAsync(name, packageVersion, packageStream, _sour using PackageArchiveReader packageReader = new PackageArchiveReader(packageStream); nuspecModel.nuspecReader = await packageReader.GetNuspecReaderAsync(_cancellationToken); - if (!_disableHashComputation) { nuspecModel.hashBytes = ComputeSha215Hash(packageStream); From dcb78f89fbc10b982e2a8be6b05f45b31047bc1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Dec 2022 22:00:44 +0000 Subject: [PATCH 03/25] Bump Moq from 4.18.2 to 4.18.4 Bumps [Moq](https://github.com/moq/moq4) from 4.18.2 to 4.18.4. - [Release notes](https://github.com/moq/moq4/releases) - [Changelog](https://github.com/moq/moq4/blob/main/CHANGELOG.md) - [Commits](https://github.com/moq/moq4/compare/v4.18.2...v4.18.4) --- updated-dependencies: - dependency-name: Moq dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- CycloneDX.Tests/CycloneDX.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CycloneDX.Tests/CycloneDX.Tests.csproj b/CycloneDX.Tests/CycloneDX.Tests.csproj index 9828a6bd..cb9ef43e 100644 --- a/CycloneDX.Tests/CycloneDX.Tests.csproj +++ b/CycloneDX.Tests/CycloneDX.Tests.csproj @@ -14,7 +14,7 @@ - + From 99692fc8c60951ef0cff39d7e0ee89c0284495cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 22:00:51 +0000 Subject: [PATCH 04/25] Bump actions/checkout from 3.1.0 to 3.3.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 3.1.0 to 3.3.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.1.0...v3.3.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnetcore.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 5327e90f..173b2a23 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v3.3.0 - name: Setup dotnet 3.1 uses: actions/setup-dotnet@v3.0.3 with: @@ -50,7 +50,7 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v3.3.0 - name: Setup dotnet 3.1 uses: actions/setup-dotnet@v3.0.3 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27d2ce64..cf7e79c1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v3.3.0 - name: Setup dotnet 3.1 uses: actions/setup-dotnet@v3.0.3 with: From 302b631e2f04018d8d8d702ec331aff3885c2eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane?= Date: Thu, 26 Jan 2023 17:24:25 +0100 Subject: [PATCH 05/25] Fix dependency name in Error log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Escandell --- CycloneDX/Services/ProjectAssetsFileService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CycloneDX/Services/ProjectAssetsFileService.cs b/CycloneDX/Services/ProjectAssetsFileService.cs index 49cc3c2d..43a54d59 100644 --- a/CycloneDX/Services/ProjectAssetsFileService.cs +++ b/CycloneDX/Services/ProjectAssetsFileService.cs @@ -166,7 +166,7 @@ private static void ResolveDependecyVersionRanges(HashSet runtimeP else { // This should not happen, since all dependencies are resolved to a specific version. - Console.Error.WriteLine($"Dependency ({dependency.Value}) with version range ({dependency.Value}) did not resolve to a specific version."); + Console.Error.WriteLine($"Dependency ({dependency.Key}) with version range ({dependency.Value}) did not resolve to a specific version."); } } } From 478c1f3481c0286366138b12a3f0e5b7dea14210 Mon Sep 17 00:00:00 2001 From: Matthew Basson Date: Mon, 16 Jan 2023 10:22:00 +0000 Subject: [PATCH 06/25] Adding .net 7 comment to ReadMe.md Signed-off-by: Matthew Basson Changing project references to include .net 7 as well legacy versions Signed-off-by: Matthew Basson Tweaking project reference configuration Signed-off-by: Matthew Basson Incrementing docker base image to use dotnet7 Signed-off-by: Matthew Basson Removing lang version constraint Signed-off-by: Matthew Basson Revert "Removing lang version constraint" This reverts commit cdb95e1c655161fc6a2b296d6b5d196db67307e6. Signed-off-by: Matthew Basson Removing LangVersion declaration Signed-off-by: Matthew Basson Adding dotnet version 7 to git hub workflow Signed-off-by: Matthew Basson Add dotnet 7 as the test release. Signed-off-by: Matthew Basson Updating the dev container to include dotnet 7 Signed-off-by: Matthew Basson Adding multi version syntax to dotnet core.yml Signed-off-by: Matthew Basson Adding multi version syntax to release.yml Signed-off-by: Matthew Basson Adding multi version syntax to release.yml with right name Signed-off-by: Matthew Basson Porting .net 7 dev container to its own version of ubuntu Signed-off-by: Matthew Basson Update to remove 3.1 and 2 support in actions Signed-off-by: Matthew Basson Update to remove 3.1 and 2 support in actions Signed-off-by: Matthew Basson removing older version Dockerfile Signed-off-by: Matthew Basson adding versions to docker script Signed-off-by: Matthew Basson removing 3.1 dotnet workflow reference Signed-off-by: Matthew Basson fixing static code analysis issues Signed-off-by: Matthew Basson fixing static code analysis issues 2 Signed-off-by: Matthew Basson Upping the dotnet framework version base image to 22.04 and removing 3.1 framework dependencies. Signed-off-by: Matthew Basson --- .devcontainer/Dockerfile | 12 ------------ .devcontainer/Ubuntu22.04/Dockerfile | 8 ++++++++ .devcontainer/devcontainer.json | 2 +- .github/workflows/dotnetcore.yml | 21 +++++++-------------- .github/workflows/release.yml | 15 ++++++--------- CycloneDX.Tests/CycloneDX.Tests.csproj | 2 +- CycloneDX/CycloneDX.csproj | 2 +- Dockerfile | 4 ++-- README.md | 4 ++-- 9 files changed, 28 insertions(+), 42 deletions(-) delete mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/Ubuntu22.04/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index a1c52fbc..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM ubuntu:20.04 - -RUN apt-get update \ - && apt-get install -y \ - wget \ - apt-transport-https \ - && wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \ - && dpkg -i packages-microsoft-prod.deb \ - && apt-get update \ - && apt-get install -y dotnet-sdk-2.1 \ - && apt-get install -y dotnet-sdk-3.1 \ - && rm -rf /var/lib/apt/lists/* diff --git a/.devcontainer/Ubuntu22.04/Dockerfile b/.devcontainer/Ubuntu22.04/Dockerfile new file mode 100644 index 00000000..b153ad56 --- /dev/null +++ b/.devcontainer/Ubuntu22.04/Dockerfile @@ -0,0 +1,8 @@ +FROM ubuntu:22.04 + +RUN apt-get install -y --no-install-recommends wget=2.0.1 +RUN apt-get install -y --no-install-recommends apt-transport-https=2.5.6 +RUN curl -o ./packages-microsoft-prod.deb https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb +RUN dpkg -i packages-microsoft-prod.deb +RUN apt-get install -y --no-install-recommends dotnet-sdk-7.0=7.0 +RUN rm -rf /var/lib/apt/lists/* diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f38a279f..8c43a082 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,7 @@ { "name": "devcontainer", "build": { - "dockerfile": "Dockerfile", + "dockerfile": "Ubuntu22.04/Dockerfile", "context": ".." }, "extensions": [ diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 5327e90f..baa76a03 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -17,14 +17,11 @@ jobs: timeout-minutes: 30 steps: - uses: actions/checkout@v3.1.0 - - name: Setup dotnet 3.1 + - name: Setup dotnet uses: actions/setup-dotnet@v3.0.3 with: - dotnet-version: '3.1.x' - - name: Setup dotnet 6 - uses: actions/setup-dotnet@v3.0.3 - with: - dotnet-version: '6.x' + dotnet-version: | + 7.x - name: Build run: dotnet build /WarnAsError @@ -46,18 +43,14 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - framework: ['netcoreapp3.1','net6.0'] + framework: ['net6.0','net7.0'] timeout-minutes: 30 - + steps: - uses: actions/checkout@v3.1.0 - - name: Setup dotnet 3.1 - uses: actions/setup-dotnet@v3.0.3 - with: - dotnet-version: '3.1.x' - - name: Setup dotnet 6 + - name: Setup dotnet 7 uses: actions/setup-dotnet@v3.0.3 with: - dotnet-version: '6.x' + dotnet-version: '7.x' - name: Tests run: dotnet test --framework ${{ matrix.framework }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27d2ce64..102b0142 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ # # For an example commit browse to # https://github.com/CycloneDX/cyclonedx-dotnet/commit/d110af854371374460430bb8438225a7d7a84274. -# +# # The resulting release is here # https://github.com/CycloneDX/cyclonedx-dotnet/releases/tag/v1.0.0. # @@ -29,18 +29,15 @@ jobs: timeout-minutes: 30 steps: - uses: actions/checkout@v3.1.0 - - name: Setup dotnet 3.1 - uses: actions/setup-dotnet@v3.0.3 - with: - dotnet-version: '3.1.x' - - name: Setup dotnet 6 + - name: Setup dotnet uses: actions/setup-dotnet@v3.0.3 with: - dotnet-version: '6.x' - + dotnet-version: | + 7.x + # The tests should have already been run during the PR workflow, so this is really just a sanity check - name: Tests - run: dotnet test --framework net6.0 + run: dotnet test --framework net7.0 # Build and package everything, including the Docker image - name: Package release diff --git a/CycloneDX.Tests/CycloneDX.Tests.csproj b/CycloneDX.Tests/CycloneDX.Tests.csproj index 9828a6bd..9c660338 100644 --- a/CycloneDX.Tests/CycloneDX.Tests.csproj +++ b/CycloneDX.Tests/CycloneDX.Tests.csproj @@ -1,10 +1,10 @@ - net6.0;netcoreapp3.1 true false + net7.0;net6.0 diff --git a/CycloneDX/CycloneDX.csproj b/CycloneDX/CycloneDX.csproj index 3f92018b..fd1c8782 100644 --- a/CycloneDX/CycloneDX.csproj +++ b/CycloneDX/CycloneDX.csproj @@ -2,7 +2,6 @@ Exe - net6.0;netcoreapp3.1 CycloneDX true A .NET Core global tool to generate CycloneDX bill-of-material documents for use with Software Composition Analysis (SCA). @@ -10,6 +9,7 @@ true dotnet-CycloneDX <_SkipUpgradeNetAnalyzersNuGetWarning>true + net7.0;net6.0 diff --git a/Dockerfile b/Dockerfile index 8afd6917..d0eaea6d 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 ARG VERSION COPY ./nupkgs /tmp/nupkgs/ @@ -6,4 +6,4 @@ RUN dotnet tool install --global CycloneDX --version ${VERSION} --add-source /tm ln -s /root/.dotnet/tools/dotnet-CycloneDX /usr/bin/CycloneDX ENTRYPOINT [ "CycloneDX" ] -CMD [ "--help" ] \ No newline at end of file +CMD [ "--help" ] diff --git a/README.md b/README.md index 81297a01..5a1f9566 100755 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ The CycloneDX module for .NET creates a valid CycloneDX bill-of-material document containing an aggregate of all project dependencies. CycloneDX is a lightweight BOM specification that is easily created, human readable, and simple to parse. This module runs on -* .NET Core 3.1 * .NET 6.0. +* .NET 7.0. This module no longer runs on - +* .NET Core 3.1 * .NET Core 2.1 * .NET5 * see https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core for more infomation From 91ab1c01116da2abccaa25b7c4f35780c486a03a Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 3 Apr 2023 10:15:03 +0200 Subject: [PATCH 07/25] use central package management and update dependencies Signed-off-by: Bert --- .gitpod.yml | 3 +-- CycloneDX.Tests/CycloneDX.Tests.csproj | 18 ++++++++-------- CycloneDX.sln | 5 +++-- CycloneDX/CycloneDX.csproj | 14 +++++------- CycloneDX/Services/ProjectFileService.cs | 4 ++-- Directory.Build.props | 9 ++------ Directory.Packages.props | 27 ++++++++++++++++++++++++ README.md | 10 ++++----- 8 files changed, 54 insertions(+), 36 deletions(-) create mode 100644 Directory.Packages.props diff --git a/.gitpod.yml b/.gitpod.yml index 8f286a18..a5e0d30b 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -8,9 +8,8 @@ tasks: wget --output-document="$DOTNET_ROOT/dotnet-install.sh" https://dot.net/v1/dotnet-install.sh chmod +x "$DOTNET_ROOT/dotnet-install.sh" "$DOTNET_ROOT/dotnet-install.sh" --channel 2.1 --install-dir "$DOTNET_ROOT" - "$DOTNET_ROOT/dotnet-install.sh" --channel 3.1 --install-dir "$DOTNET_ROOT" - "$DOTNET_ROOT/dotnet-install.sh" --channel 5.0 --install-dir "$DOTNET_ROOT" "$DOTNET_ROOT/dotnet-install.sh" --channel 6.0 --install-dir "$DOTNET_ROOT" + "$DOTNET_ROOT/dotnet-install.sh" --channel 7.0 --install-dir "$DOTNET_ROOT" dotnet tool install --global dotnet-reportgenerator-globaltool dotnet restore diff --git a/CycloneDX.Tests/CycloneDX.Tests.csproj b/CycloneDX.Tests/CycloneDX.Tests.csproj index 1c508f47..97f904ec 100644 --- a/CycloneDX.Tests/CycloneDX.Tests.csproj +++ b/CycloneDX.Tests/CycloneDX.Tests.csproj @@ -8,21 +8,21 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - - - + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/CycloneDX.sln b/CycloneDX.sln index 78d8a988..aa67b466 100644 --- a/CycloneDX.sln +++ b/CycloneDX.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30524.135 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33103.201 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CycloneDX", "CycloneDX\CycloneDX.csproj", "{88DFA76C-1C0A-4A83-AA48-EA1D28A9ABED}" EndProject @@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig build.sh = build.sh Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props Dockerfile = Dockerfile LICENSE = LICENSE nuget.config = nuget.config diff --git a/CycloneDX/CycloneDX.csproj b/CycloneDX/CycloneDX.csproj index fd1c8782..76dc97f0 100644 --- a/CycloneDX/CycloneDX.csproj +++ b/CycloneDX/CycloneDX.csproj @@ -23,14 +23,10 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + diff --git a/CycloneDX/Services/ProjectFileService.cs b/CycloneDX/Services/ProjectFileService.cs index d1bf17bf..120faa69 100755 --- a/CycloneDX/Services/ProjectFileService.cs +++ b/CycloneDX/Services/ProjectFileService.cs @@ -195,7 +195,7 @@ public async Task> GetProjectReferencesAsync(string projectFileP Console.WriteLine(" Getting project references"); var projectReferences = new HashSet(); - var projectDirectory = _fileSystem.FileInfo.FromFileName(projectFilePath).Directory.FullName; + var projectDirectory = _fileSystem.FileInfo.New(projectFilePath).Directory.FullName; using (StreamReader fileReader = _fileSystem.File.OpenText(projectFilePath)) { @@ -240,7 +240,7 @@ public async Task> RecursivelyGetProjectReferencesAsync(string p // Initialize the queue with the current project file var files = new Queue(); - files.Enqueue(_fileSystem.FileInfo.FromFileName(projectFilePath).FullName); + files.Enqueue(_fileSystem.FileInfo.New(projectFilePath).FullName); var visitedProjectFiles = new HashSet(); diff --git a/Directory.Build.props b/Directory.Build.props index cffe12cf..968b97c5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,14 +19,9 @@ AllEnableByDefault - - + true + Steve Springett & Patrick Dwyer diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 00000000..662e078b --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,27 @@ + + + true + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 5a1f9566..eb26e90e 100755 --- a/README.md +++ b/README.md @@ -13,14 +13,14 @@ The CycloneDX module for .NET creates a valid CycloneDX bill-of-material document containing an aggregate of all project dependencies. CycloneDX is a lightweight BOM specification that is easily created, human readable, and simple to parse. This module runs on -* .NET 6.0. -* .NET 7.0. +* .NET 6.0 +* .NET 7.0 This module no longer runs on -* .NET Core 3.1 * .NET Core 2.1 -* .NET5 -* see https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core for more infomation +* .NET Core 3.1 +* .NET 5.0 +* see https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core for more information ## Usage From ab3126f09bd1bdf0f891d2392a3ee1c4db82bad2 Mon Sep 17 00:00:00 2001 From: Arthur van Dongen Date: Mon, 3 Apr 2023 17:40:08 +0200 Subject: [PATCH 08/25] NORMALIZE VERSION STRINGS THE SAME AS NUGET DOES Nuget will normalize the version string according to https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#normalized-version-numbers, when creating the local cache. Thus, a version "1.0.0.0" will be stored in folder "1.0.0" and the nupkg and nupkg.sha512 files will be named accordingly. This change adds a Normalize method to strip off the Revision number under certain conditions, before locating the files in the cache. This fixes issue #673. Signed-off-by: Arthur van Dongen --- CycloneDX.Tests/NugetV3ServiceTests.cs | 47 +++++++++++++++++--------- CycloneDX/Services/NugetV3Service.cs | 27 ++++++++++++--- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/CycloneDX.Tests/NugetV3ServiceTests.cs b/CycloneDX.Tests/NugetV3ServiceTests.cs index 9ead85b6..b167c7f3 100644 --- a/CycloneDX.Tests/NugetV3ServiceTests.cs +++ b/CycloneDX.Tests/NugetV3ServiceTests.cs @@ -80,37 +80,52 @@ public async Task GetComponent_FromCachedNuspecFile_ReturnsComponent() Assert.Equal("testpackage", component.Name); } - [Fact] - public async Task GetComponent_FromCachedNugetHashFile_ReturnsComponentWithHash() + public static IEnumerable VersionNormalization { - var nuspecFileContents = @" + get + { + return new List + { + new object[] { "2.5", "2.5" }, + new object[] { "2.5.0.0", "2.5.0" }, + new object[] { "2.5.0.0-beta.1", "2.5.0-beta.1" }, + new object[] { "2.5.1.0", "2.5.1" }, + new object[] { "2.5.1.1", "2.5.1.1" } + }; + } + } + + [Theory] + [MemberData(nameof(VersionNormalization))] + public async Task GetComponent_FromCachedNuspecFile_UsesNormalizedVersions(string rawVersion, string normalizedVersion) + { + var nuspecFileContents = $@" testpackage + {rawVersion} "; - byte[] sampleHash = new byte[] { 1, 2, 3, 4, 5, 6, 78, 125, 200 }; - - var nugetHashFileContents = Convert.ToBase64String(sampleHash); var mockFileSystem = new MockFileSystem(new Dictionary { - { XFS.Path(@"c:\nugetcache\testpackage\1.0.0\testpackage.nuspec"), new MockFileData(nuspecFileContents) }, - { XFS.Path(@"c:\nugetcache\testpackage\1.0.0\testpackage.1.0.0.nupkg.sha512"), new MockFileData(nugetHashFileContents) }, + { XFS.Path($@"c:\nugetcache\testpackage\{normalizedVersion}\testpackage.nuspec"), new MockFileData(nuspecFileContents) }, }); + var nugetService = new NugetV3Service(null, mockFileSystem, new List { XFS.Path(@"c:\nugetcache") }, new Mock().Object, new NullLogger(), false); - var component = await nugetService.GetComponentAsync("testpackage", "1.0.0", Component.ComponentScope.Required).ConfigureAwait(false); + var component = await nugetService.GetComponentAsync("testpackage", rawVersion, Component.ComponentScope.Required).ConfigureAwait(false); - Assert.Equal(Hash.HashAlgorithm.SHA_512, component.Hashes[0].Alg); - Assert.Equal(BitConverter.ToString(sampleHash).Replace("-", string.Empty), component.Hashes[0].Content); + Assert.Equal("testpackage", component.Name); + Assert.Equal(rawVersion, component.Version); } - [Fact] - public async Task GetComponent_FromCachedNugetFile_ReturnsComponentWithHash() + [Theory] + [MemberData(nameof(VersionNormalization))] + public async Task GetComponent_FromCachedNugetFile_ReturnsComponentWithHashUsingNormalizedVersion(string rawVersion, string normalizedVersion) { var nuspecFileContents = @" @@ -122,8 +137,8 @@ public async Task GetComponent_FromCachedNugetFile_ReturnsComponentWithHash() var nugetFileContent = "FooBarBaz"; var mockFileSystem = new MockFileSystem(new Dictionary { - { XFS.Path(@"c:\nugetcache\testpackage\1.0.0\testpackage.nuspec"), new MockFileData(nuspecFileContents) }, - { XFS.Path(@"c:\nugetcache\testpackage\1.0.0\testpackage.1.0.0.nupkg"), new MockFileData(nugetFileContent) }, + { XFS.Path($@"c:\nugetcache\testpackage\{normalizedVersion}\testpackage.nuspec"), new MockFileData(nuspecFileContents) }, + { XFS.Path($@"c:\nugetcache\testpackage\{normalizedVersion}\testpackage.{normalizedVersion}.nupkg"), new MockFileData(nugetFileContent) }, }); var nugetService = new NugetV3Service(null, @@ -132,7 +147,7 @@ public async Task GetComponent_FromCachedNugetFile_ReturnsComponentWithHash() new Mock().Object, new NullLogger(), false); - var component = await nugetService.GetComponentAsync("testpackage", "1.0.0", Component.ComponentScope.Required).ConfigureAwait(false); + var component = await nugetService.GetComponentAsync("testpackage", $"{rawVersion}", Component.ComponentScope.Required).ConfigureAwait(false); byte[] hashBytes; using (SHA512 sha = SHA512.Create()) diff --git a/CycloneDX/Services/NugetV3Service.cs b/CycloneDX/Services/NugetV3Service.cs index 11d0fda5..064ecef0 100644 --- a/CycloneDX/Services/NugetV3Service.cs +++ b/CycloneDX/Services/NugetV3Service.cs @@ -84,7 +84,7 @@ internal string GetCachedNuspecFilename(string name, string version) foreach (var packageCachePath in _packageCachePaths) { - var currentDirectory = _fileSystem.Path.Combine(packageCachePath, lowerName, version); + var currentDirectory = _fileSystem.Path.Combine(packageCachePath, lowerName, NormalizeVersion(version)); var currentFilename = _fileSystem.Path.Combine(currentDirectory, lowerName + _nuspecExtension); if (_fileSystem.File.Exists(currentFilename)) { @@ -96,6 +96,24 @@ internal string GetCachedNuspecFilename(string name, string version) return nuspecFilename; } + /// + /// Normalize the version string according to + /// https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#normalized-version-numbers + /// + private string NormalizeVersion(string version) + { + var separator = Math.Max(version.IndexOf('-'), version.IndexOf('+')); + var part1 = separator < 0 ? version : version.Substring(0, separator); + var part2 = separator < 0 ? string.Empty : version.Substring(separator); + if (Version.TryParse(part1, out var parsed) && parsed.Revision == 0) + { + part1 = parsed.ToString(3); + version = part1 + part2; + } + + return version; + } + private SourceRepository SetupNugetRepository(NugetInputModel nugetInput) { if (nugetInput == null || string.IsNullOrEmpty(nugetInput.nugetFeedUrl) || @@ -153,9 +171,7 @@ public async Task GetComponentAsync(string name, string version, Comp var component = SetupComponent(name, version, scope); var nuspecFilename = GetCachedNuspecFilename(name, version); - var nuspecModel = await GetNuspec(name, version, nuspecFilename, resource).ConfigureAwait(false); - if (nuspecModel.hashBytes != null) { var hex = BitConverter.ToString(nuspecModel.hashBytes).Replace("-", string.Empty); @@ -322,8 +338,9 @@ await resource.CopyNupkgToStreamAsync(name, packageVersion, packageStream, _sour // ├─..nupkg.sha512 // └─.nuspec - string shaFilename = Path.ChangeExtension(nuspecFilename, version + _sha512Extension); - string nupkgFilename = Path.ChangeExtension(nuspecFilename, version + _nupkgExtension); + var normalizedVersion = NormalizeVersion(version); + string shaFilename = Path.ChangeExtension(nuspecFilename, normalizedVersion + _sha512Extension); + string nupkgFilename = Path.ChangeExtension(nuspecFilename, normalizedVersion + _nupkgExtension); if (_fileSystem.File.Exists(shaFilename)) { From 2ef7cb3f8fb45a4b4486f07df3142d47c5987f34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 22:56:40 +0000 Subject: [PATCH 09/25] Bump actions/checkout from 3.3.0 to 3.5.2 Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.5.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.3.0...v3.5.2) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dotnetcore.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index d24c89eb..066bb60e 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.5.2 - name: Setup dotnet uses: actions/setup-dotnet@v3.0.3 with: @@ -47,7 +47,7 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.5.2 - name: Setup dotnet 7 uses: actions/setup-dotnet@v3.0.3 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 806fd4d3..4358f279 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.5.2 - name: Setup dotnet uses: actions/setup-dotnet@v3.0.3 with: From af7fc131d6a69b1b9855d0be9ba79de2b9ca8a1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Apr 2023 07:58:33 +0000 Subject: [PATCH 10/25] Bump System.IO.Abstractions from 17.2.3 to 19.2.16 Bumps [System.IO.Abstractions](https://github.com/TestableIO/System.IO.Abstractions) from 17.2.3 to 19.2.16. - [Release notes](https://github.com/TestableIO/System.IO.Abstractions/releases) - [Commits](https://github.com/TestableIO/System.IO.Abstractions/compare/v17.2.3...v19.2.16) --- updated-dependencies: - dependency-name: System.IO.Abstractions dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 662e078b..5aff8bd8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,7 +21,7 @@ - + From d39127a1eb8447cebe77f242ff4e3e51880d36e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Apr 2023 07:58:40 +0000 Subject: [PATCH 11/25] Bump System.IO.Abstractions.TestingHelpers from 17.2.3 to 19.2.16 Bumps [System.IO.Abstractions.TestingHelpers](https://github.com/TestableIO/System.IO.Abstractions) from 17.2.3 to 19.2.16. - [Release notes](https://github.com/TestableIO/System.IO.Abstractions/releases) - [Commits](https://github.com/TestableIO/System.IO.Abstractions/compare/v17.2.3...v19.2.16) --- updated-dependencies: - dependency-name: System.IO.Abstractions.TestingHelpers dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 662e078b..7821dc15 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,6 +22,6 @@ - + From 352399ff4741d8151c25dc015beb2905781db59b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Apr 2023 22:56:46 +0000 Subject: [PATCH 12/25] Bump CycloneDX.Core from 5.3.2 to 5.4.0 Bumps [CycloneDX.Core](https://github.com/CycloneDX/cyclonedx-dotnet-library) from 5.3.2 to 5.4.0. - [Release notes](https://github.com/CycloneDX/cyclonedx-dotnet-library/releases) - [Commits](https://github.com/CycloneDX/cyclonedx-dotnet-library/compare/v5.3.2...v5.4.0) --- updated-dependencies: - dependency-name: CycloneDX.Core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a52a199a..dfff0025 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ - + From ccba2519426d6a7cd88a6a1b51f7c48a7a3ff36c Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Mon, 24 Apr 2023 12:29:39 +0200 Subject: [PATCH 13/25] fix: populate authors as Author, not Publisher Co-Authored-By: Florian Greinacher Signed-off-by: Nejc Habjan --- CycloneDX/Services/NugetV3Service.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CycloneDX/Services/NugetV3Service.cs b/CycloneDX/Services/NugetV3Service.cs index ef681b9b..4dde3c55 100644 --- a/CycloneDX/Services/NugetV3Service.cs +++ b/CycloneDX/Services/NugetV3Service.cs @@ -279,7 +279,7 @@ public async Task GetComponentAsync(string name, string version, Comp private static Component SetupComponentProperties(Component component, NuspecModel nuspecModel) { - component.Publisher = nuspecModel.nuspecReader.GetAuthors(); + component.Author = nuspecModel.nuspecReader.GetAuthors(); component.Copyright = nuspecModel.nuspecReader.GetCopyright(); // this prevents empty copyright values in the JSON BOM if (string.IsNullOrEmpty(component.Copyright)) From 230025279f931195c2ecc9074a78b7fe740e87af Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 2 May 2023 10:54:53 +0200 Subject: [PATCH 14/25] use suppressParent to identify development dependencies Signed-off-by: Bert --- CycloneDX.Tests/ProgramTests.cs | 4 +-- .../ProjectAssetsFileServiceTests.cs | 4 +-- CycloneDX.Tests/ProjectFileServiceTests.cs | 20 +++++------ .../Interfaces/IProjectAssetsFileService.cs | 2 +- CycloneDX/Interfaces/IProjectFileService.cs | 4 +-- CycloneDX/Interfaces/ISolutionFileService.cs | 2 +- CycloneDX/Models/NugetPackage.cs | 1 + CycloneDX/Program.cs | 6 ++-- .../Services/ProjectAssetsFileService.cs | 35 +++++++++++++------ CycloneDX/Services/ProjectFileService.cs | 10 +++--- CycloneDX/Services/SolutionFileService.cs | 4 +-- 11 files changed, 54 insertions(+), 38 deletions(-) diff --git a/CycloneDX.Tests/ProgramTests.cs b/CycloneDX.Tests/ProgramTests.cs index 084211b4..06f09248 100755 --- a/CycloneDX.Tests/ProgramTests.cs +++ b/CycloneDX.Tests/ProgramTests.cs @@ -55,7 +55,7 @@ public async Task CallingCycloneDX_CreatesOutputDirectory() }); var mockSolutionFileService = new Mock(); mockSolutionFileService - .Setup(s => s.GetSolutionNugetPackages(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.GetSolutionNugetPackages(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new HashSet()); Program.fileSystem = mockFileSystem; Program.solutionFileService = mockSolutionFileService.Object; @@ -80,7 +80,7 @@ public async Task CallingCycloneDX_WithOutputFilename_CreatesOutputFilename() }); var mockSolutionFileService = new Mock(); mockSolutionFileService - .Setup(s => s.GetSolutionNugetPackages(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.GetSolutionNugetPackages(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new HashSet()); Program.fileSystem = mockFileSystem; Program.solutionFileService = mockSolutionFileService.Object; diff --git a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs index 75f18a6b..8653c038 100644 --- a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs +++ b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs @@ -114,7 +114,7 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in }); var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () => mockAssetReader.Object); - var packages = projectAssetsFileService.GetNugetPackages(XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), false); + var packages = projectAssetsFileService.GetNugetPackages(XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), false, false); var sortedPackages = new List(packages); sortedPackages.Sort(); @@ -205,7 +205,7 @@ public void GetNugetPackages_MissingResolvedPackageVersion(string framework, int }); var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () => mockAssetReader.Object); - var packages = projectAssetsFileService.GetNugetPackages(XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), false); + var packages = projectAssetsFileService.GetNugetPackages(XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), false, false); var sortedPackages = new List(packages); sortedPackages.Sort(); diff --git a/CycloneDX.Tests/ProjectFileServiceTests.cs b/CycloneDX.Tests/ProjectFileServiceTests.cs index b730cbf5..4a1d06b9 100755 --- a/CycloneDX.Tests/ProjectFileServiceTests.cs +++ b/CycloneDX.Tests/ProjectFileServiceTests.cs @@ -68,7 +68,7 @@ public async Task GetProjectNugetPackages_WithProjectAssetsFile_ReturnsNugetPack var mockPackageFileService = new Mock(); var mockProjectAssetsFileService = new Mock(); mockProjectAssetsFileService - .Setup(s => s.GetNugetPackages(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.GetNugetPackages(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new HashSet { new NugetPackage { Name = "Package", Version = "1.2.3" }, @@ -79,7 +79,7 @@ public async Task GetProjectNugetPackages_WithProjectAssetsFile_ReturnsNugetPack mockPackageFileService.Object, mockProjectAssetsFileService.Object); - var packages = await projectFileService.GetProjectNugetPackagesAsync(XFS.Path(@"c:\Project\Project.csproj"), "", false, "", "").ConfigureAwait(false); + var packages = await projectFileService.GetProjectNugetPackagesAsync(XFS.Path(@"c:\Project\Project.csproj"), "", false, false, "", "").ConfigureAwait(false); Assert.Collection(packages, item => { @@ -103,7 +103,7 @@ public async Task GetProjectNugetPackages_WithProjectAssetsFileWithoutRestore_Re var mockPackageFileService = new Mock(); var mockProjectAssetsFileService = new Mock(); mockProjectAssetsFileService - .Setup(s => s.GetNugetPackages(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.GetNugetPackages(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new HashSet { new NugetPackage { Name = "Package", Version = "1.2.3" }, @@ -115,7 +115,7 @@ public async Task GetProjectNugetPackages_WithProjectAssetsFileWithoutRestore_Re mockProjectAssetsFileService.Object); projectFileService.DisablePackageRestore = true; - var packages = await projectFileService.GetProjectNugetPackagesAsync(XFS.Path(@"c:\Project\Project.csproj"), "", false, "", "").ConfigureAwait(false); + var packages = await projectFileService.GetProjectNugetPackagesAsync(XFS.Path(@"c:\Project\Project.csproj"), "", false, false, "", "").ConfigureAwait(false); Assert.Collection(packages, item => { @@ -139,7 +139,7 @@ public async Task GetProjectNugetPackages_WithProjectAssetsFile_ReturnsMultipleN var mockPackageFileService = new Mock(); var mockProjectAssetsFileService = new Mock(); mockProjectAssetsFileService - .Setup(s => s.GetNugetPackages(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.GetNugetPackages(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new HashSet { new NugetPackage { Name = "Package1", Version = "1.2.3" }, @@ -152,7 +152,7 @@ public async Task GetProjectNugetPackages_WithProjectAssetsFile_ReturnsMultipleN mockPackageFileService.Object, mockProjectAssetsFileService.Object); - var packages = await projectFileService.GetProjectNugetPackagesAsync(XFS.Path(@"c:\Project\Project.csproj"), "", false, "", "").ConfigureAwait(false); + var packages = await projectFileService.GetProjectNugetPackagesAsync(XFS.Path(@"c:\Project\Project.csproj"), "", false, false, "", "").ConfigureAwait(false); var sortedPackages = new List(packages); sortedPackages.Sort(); @@ -185,7 +185,7 @@ public async Task GetProjectNugetPackages_WithPackagesConfig_ReturnsNugetPackage ); var mockProjectAssetsFileService = new Mock(); mockProjectAssetsFileService - .Setup(s => s.GetNugetPackages(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.GetNugetPackages(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new HashSet()); var projectFileService = new ProjectFileService( mockFileSystem, @@ -193,7 +193,7 @@ public async Task GetProjectNugetPackages_WithPackagesConfig_ReturnsNugetPackage mockPackageFileService.Object, mockProjectAssetsFileService.Object); - var packages = await projectFileService.GetProjectNugetPackagesAsync(XFS.Path(@"c:\Project\Project.csproj"), "", false, "", "").ConfigureAwait(false); + var packages = await projectFileService.GetProjectNugetPackagesAsync(XFS.Path(@"c:\Project\Project.csproj"), "", false, false, "", "").ConfigureAwait(false); Assert.Collection(packages, item => { @@ -227,7 +227,7 @@ public async Task GetProjectNugetPackages_WithPackagesConfig_ReturnsMultipleNuge ); var mockProjectAssetsFileService = new Mock(); mockProjectAssetsFileService - .Setup(s => s.GetNugetPackages(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.GetNugetPackages(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new HashSet()); var projectFileService = new ProjectFileService( mockFileSystem, @@ -235,7 +235,7 @@ public async Task GetProjectNugetPackages_WithPackagesConfig_ReturnsMultipleNuge mockPackageFileService.Object, mockProjectAssetsFileService.Object); - var packages = await projectFileService.GetProjectNugetPackagesAsync(XFS.Path(@"c:\Project\Project.csproj"), "", false, "", "").ConfigureAwait(false); + var packages = await projectFileService.GetProjectNugetPackagesAsync(XFS.Path(@"c:\Project\Project.csproj"), "", false, false, "", "").ConfigureAwait(false); var sortedPackages = new List(packages); sortedPackages.Sort(); diff --git a/CycloneDX/Interfaces/IProjectAssetsFileService.cs b/CycloneDX/Interfaces/IProjectAssetsFileService.cs index 9900c8e3..732fd974 100644 --- a/CycloneDX/Interfaces/IProjectAssetsFileService.cs +++ b/CycloneDX/Interfaces/IProjectAssetsFileService.cs @@ -22,6 +22,6 @@ namespace CycloneDX.Interfaces { public interface IProjectAssetsFileService { - HashSet GetNugetPackages(string projectFilePath, string projectAssetsFilePath, bool IsTestProject); + HashSet GetNugetPackages(string projectFilePath, string projectAssetsFilePath, bool IsTestProject, bool excludeDev); } } diff --git a/CycloneDX/Interfaces/IProjectFileService.cs b/CycloneDX/Interfaces/IProjectFileService.cs index 91267138..9b56e8eb 100755 --- a/CycloneDX/Interfaces/IProjectFileService.cs +++ b/CycloneDX/Interfaces/IProjectFileService.cs @@ -24,8 +24,8 @@ namespace CycloneDX.Interfaces public interface IProjectFileService { bool DisablePackageRestore { get; set; } - Task> GetProjectNugetPackagesAsync(string projectFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, string framework, string runtime); - Task> RecursivelyGetProjectNugetPackagesAsync(string projectFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, string framework, string runtime); + Task> GetProjectNugetPackagesAsync(string projectFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, bool excludeDev, string framework, string runtime); + Task> RecursivelyGetProjectNugetPackagesAsync(string projectFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, bool excludeDev, string framework, string runtime); Task> GetProjectReferencesAsync(string projectFilePath); Task> RecursivelyGetProjectReferencesAsync(string projectFilePath); } diff --git a/CycloneDX/Interfaces/ISolutionFileService.cs b/CycloneDX/Interfaces/ISolutionFileService.cs index c06b0782..3690e729 100755 --- a/CycloneDX/Interfaces/ISolutionFileService.cs +++ b/CycloneDX/Interfaces/ISolutionFileService.cs @@ -24,6 +24,6 @@ namespace CycloneDX.Interfaces public interface ISolutionFileService { Task> GetSolutionProjectReferencesAsync(string solutionFilePath); - Task> GetSolutionNugetPackages(string solutionFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, string framework, string runtime); + Task> GetSolutionNugetPackages(string solutionFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, bool excludeDev, string framework, string runtime); } } diff --git a/CycloneDX/Models/NugetPackage.cs b/CycloneDX/Models/NugetPackage.cs index 4fcdf47e..b3dc0d79 100644 --- a/CycloneDX/Models/NugetPackage.cs +++ b/CycloneDX/Models/NugetPackage.cs @@ -27,6 +27,7 @@ public class NugetPackage : IComparable public string Name { get; set; } public string Version { get; set; } public bool IsDirectReference { get; set; } + public bool IsDevDependency { get; set; } public Component.ComponentScope? Scope { get; set; } public Dictionary Dependencies { get; set; } //key: name ~ value: version diff --git a/CycloneDX/Program.cs b/CycloneDX/Program.cs index 26e6e83d..29466172 100755 --- a/CycloneDX/Program.cs +++ b/CycloneDX/Program.cs @@ -240,17 +240,17 @@ async Task OnExecuteAsync(CommandLineApplication app) { { if (SolutionOrProjectFile.ToLowerInvariant().EndsWith(".sln", StringComparison.OrdinalIgnoreCase)) { - packages = await solutionFileService.GetSolutionNugetPackages(fullSolutionOrProjectFilePath, baseIntermediateOutputPath, excludetestprojects, framework, runtime).ConfigureAwait(false); + packages = await solutionFileService.GetSolutionNugetPackages(fullSolutionOrProjectFilePath, baseIntermediateOutputPath, excludetestprojects, excludeDev, framework, runtime).ConfigureAwait(false); topLevelComponent.Name = fileSystem.Path.GetFileNameWithoutExtension(SolutionOrProjectFile); } else if (Utils.IsSupportedProjectType(SolutionOrProjectFile) && scanProjectReferences) { - packages = await projectFileService.RecursivelyGetProjectNugetPackagesAsync(fullSolutionOrProjectFilePath, baseIntermediateOutputPath, excludetestprojects, framework, runtime).ConfigureAwait(false); + packages = await projectFileService.RecursivelyGetProjectNugetPackagesAsync(fullSolutionOrProjectFilePath, baseIntermediateOutputPath, excludetestprojects, excludeDev, framework, runtime).ConfigureAwait(false); topLevelComponent.Name = fileSystem.Path.GetFileNameWithoutExtension(SolutionOrProjectFile); } else if (Utils.IsSupportedProjectType(SolutionOrProjectFile)) { - packages = await projectFileService.GetProjectNugetPackagesAsync(fullSolutionOrProjectFilePath, baseIntermediateOutputPath, excludetestprojects, framework, runtime).ConfigureAwait(false); + packages = await projectFileService.GetProjectNugetPackagesAsync(fullSolutionOrProjectFilePath, baseIntermediateOutputPath, excludetestprojects, excludeDev, framework, runtime).ConfigureAwait(false); topLevelComponent.Name = fileSystem.Path.GetFileNameWithoutExtension(SolutionOrProjectFile); } else if (Program.fileSystem.Path.GetFileName(SolutionOrProjectFile).ToLowerInvariant().Equals("packages.config", StringComparison.OrdinalIgnoreCase)) diff --git a/CycloneDX/Services/ProjectAssetsFileService.cs b/CycloneDX/Services/ProjectAssetsFileService.cs index 43a54d59..7bb0a97c 100644 --- a/CycloneDX/Services/ProjectAssetsFileService.cs +++ b/CycloneDX/Services/ProjectAssetsFileService.cs @@ -22,6 +22,8 @@ using System.Linq; using CycloneDX.Interfaces; using NuGet.Versioning; +using System.IO; +using System.Text.Json; namespace CycloneDX.Services { @@ -38,12 +40,17 @@ public ProjectAssetsFileService(IFileSystem fileSystem, IDotnetCommandService do _assetFileReaderFactory = assetFileReaderFactory; } - public HashSet GetNugetPackages(string projectFilePath, string projectAssetsFilePath, bool isTestProject) + public HashSet GetNugetPackages(string projectFilePath, string projectAssetsFilePath, bool isTestProject, bool excludeDev) { var packages = new HashSet(); if (_fileSystem.File.Exists(projectAssetsFilePath)) { + var jsonContent = File.ReadAllText(projectAssetsFilePath); + var assetFileObject = JsonDocument.Parse(jsonContent); + // get all direct nuget dependencies of the project + var frameworksProperties = assetFileObject.RootElement.GetProperty("project").GetProperty("frameworks"); + var assetFileReader = _assetFileReaderFactory(); var assetsFile = assetFileReader.Read(projectAssetsFilePath); @@ -59,6 +66,8 @@ public HashSet GetNugetPackages(string projectFilePath, string pro Version = library.Version.ToNormalizedString(), Scope = Component.ComponentScope.Required, Dependencies = new Dictionary(), + // get value from project.assets.json file ( x."project"."frameworks".."dependencies".."suppressParent") + IsDevDependency = SetIsDevDependency(library.Name, targetRuntime.Name, frameworksProperties) }; var topLevelReferenceKey = (package.Name, package.Version); if (directPackageDependencies.Contains(topLevelReferenceKey)) @@ -68,15 +77,7 @@ public HashSet GetNugetPackages(string projectFilePath, string pro // is this a test project dependency or only a development dependency if ( isTestProject - || ( - library.CompileTimeAssemblies.Count == 0 - && library.ContentFiles.Count == 0 - && library.EmbedAssemblies.Count == 0 - && library.FrameworkAssemblies.Count == 0 - && library.NativeLibraries.Count == 0 - && library.ResourceAssemblies.Count == 0 - && library.ToolsAssemblies.Count == 0 - ) + || (package.IsDevDependency && excludeDev) ) { package.Scope = Component.ComponentScope.Excluded; @@ -121,6 +122,20 @@ public HashSet GetNugetPackages(string projectFilePath, string pro return directPackageDependencies; } + public bool SetIsDevDependency(string packageName, string targetRuntime, JsonElement jsonContent) + { + string framework = TargetFrameworkToAlias(targetRuntime); + JsonElement packageProperties; + var output = System.Text.Json.JsonSerializer.Serialize(jsonContent); + if (jsonContent.GetProperty(framework).GetProperty("dependencies").TryGetProperty(packageName, out packageProperties)) + { + output = System.Text.Json.JsonSerializer.Serialize(packageProperties); + // suppressParent: exists only for development dependencies + return packageProperties.TryGetProperty("suppressParent", out _); + } + return false; + } + /// /// Converts an asset file's target framework value into a csproj target framework value. /// diff --git a/CycloneDX/Services/ProjectFileService.cs b/CycloneDX/Services/ProjectFileService.cs index 120faa69..ac77d9c6 100755 --- a/CycloneDX/Services/ProjectFileService.cs +++ b/CycloneDX/Services/ProjectFileService.cs @@ -102,7 +102,7 @@ static internal String GetProjectProperty(string projectFilePath, string baseInt /// /// /// - public async Task> GetProjectNugetPackagesAsync(string projectFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, string framework, string runtime) + public async Task> GetProjectNugetPackagesAsync(string projectFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, bool excludeDev, string framework, string runtime) { if (!_fileSystem.File.Exists(projectFilePath)) { @@ -142,7 +142,7 @@ public async Task> GetProjectNugetPackagesAsync(string pro { Console.WriteLine($"File not found: \"{assetsFilename}\", \"{projectFilePath}\" "); } - var packages = _projectAssetsFileService.GetNugetPackages(projectFilePath, assetsFilename, isTestProject); + var packages = _projectAssetsFileService.GetNugetPackages(projectFilePath, assetsFilename, isTestProject, excludeDev); // if there are no project file package references look for a packages.config @@ -165,13 +165,13 @@ public async Task> GetProjectNugetPackagesAsync(string pro /// /// /// - public async Task> RecursivelyGetProjectNugetPackagesAsync(string projectFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, string framework, string runtime) + public async Task> RecursivelyGetProjectNugetPackagesAsync(string projectFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, bool excludeDev, string framework, string runtime) { - var nugetPackages = await GetProjectNugetPackagesAsync(projectFilePath, baseIntermediateOutputPath, excludeTestProjects, framework, runtime).ConfigureAwait(false); + var nugetPackages = await GetProjectNugetPackagesAsync(projectFilePath, baseIntermediateOutputPath, excludeTestProjects, excludeDev, framework, runtime).ConfigureAwait(false); var projectReferences = await RecursivelyGetProjectReferencesAsync(projectFilePath).ConfigureAwait(false); foreach (var project in projectReferences) { - var projectNugetPackages = await GetProjectNugetPackagesAsync(project, baseIntermediateOutputPath, excludeTestProjects, framework, runtime).ConfigureAwait(false); + var projectNugetPackages = await GetProjectNugetPackagesAsync(project, baseIntermediateOutputPath, excludeTestProjects, excludeDev, framework, runtime).ConfigureAwait(false); nugetPackages.UnionWith(projectNugetPackages); } return nugetPackages; diff --git a/CycloneDX/Services/SolutionFileService.cs b/CycloneDX/Services/SolutionFileService.cs index cf4f998f..3007778e 100755 --- a/CycloneDX/Services/SolutionFileService.cs +++ b/CycloneDX/Services/SolutionFileService.cs @@ -82,7 +82,7 @@ public async Task> GetSolutionProjectReferencesAsync(string solu /// /// /// - public async Task> GetSolutionNugetPackages(string solutionFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, string framework, string runtime) + public async Task> GetSolutionNugetPackages(string solutionFilePath, string baseIntermediateOutputPath, bool excludeTestProjects, bool excludeDev, string framework, string runtime) { if (!_fileSystem.File.Exists(solutionFilePath)) { @@ -113,7 +113,7 @@ public async Task> GetSolutionNugetPackages(string solutio foreach (var projectFilePath in projectQuery) { Console.WriteLine(); - var projectPackages = await _projectFileService.GetProjectNugetPackagesAsync(projectFilePath, baseIntermediateOutputPath, excludeTestProjects, framework, runtime).ConfigureAwait(false); + var projectPackages = await _projectFileService.GetProjectNugetPackagesAsync(projectFilePath, baseIntermediateOutputPath, excludeTestProjects, excludeDev, framework, runtime).ConfigureAwait(false); directReferencePackages.UnionWith(projectPackages.Where(p => p.IsDirectReference)); packages.UnionWith(projectPackages); } From dbf09d3d2fbfaee148df936417a0c5eaac0d523e Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 5 May 2023 14:01:04 +0200 Subject: [PATCH 15/25] update unit tests Signed-off-by: Bert --- CycloneDX.Tests/CycloneDX.Tests.csproj | 7 +- .../ProjectAssetsFileServiceTests.cs | 186 +++++++++++++++++- CycloneDX/CycloneDX.csproj | 4 + CycloneDX/Interfaces/IAssetFileReader.cs | 5 + CycloneDX/Interfaces/IJsonDocs .cs | 27 +++ CycloneDX/Program.cs | 4 +- CycloneDX/Services/JsonDocs.cs | 32 +++ .../Services/ProjectAssetsFileService.cs | 52 ++--- 8 files changed, 274 insertions(+), 43 deletions(-) create mode 100644 CycloneDX/Interfaces/IJsonDocs .cs create mode 100644 CycloneDX/Services/JsonDocs.cs diff --git a/CycloneDX.Tests/CycloneDX.Tests.csproj b/CycloneDX.Tests/CycloneDX.Tests.csproj index 97f904ec..9e42244f 100644 --- a/CycloneDX.Tests/CycloneDX.Tests.csproj +++ b/CycloneDX.Tests/CycloneDX.Tests.csproj @@ -5,10 +5,11 @@ true false net7.0;net6.0 + latest - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -18,11 +19,11 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs index 8653c038..6e4bf88e 100644 --- a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs +++ b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs @@ -27,11 +27,144 @@ using NuGet.ProjectModel; using NuGet.Packaging.Core; using NuGet.Versioning; +using System.Text.Json; namespace CycloneDX.Tests { public class ProjectAssetsFileServiceTests { + + readonly string jsonString1 = /*lang=json,strict*/ """ +{ + "version": 3, + "libraries": { + "Package1/1.5.0": { + "files": [ + "Package1.dll" + ], + "path": "Package1/1.5.0", + "type": "package" + }, + "Package2/4.5.1": { + "files": [ + "Package2.dll" + ], + "path": "Package2/4.5.1", + "type": "package" + } + }, + "project": { + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "Package1": { + "target": "Package", + "version": "[1.6.0,)" + } + } + }, + "netstandard2.1": { + "targetAlias": "netstandard2.1", + "dependencies": { + "Package1": { + "target": "Package", + "version": "[1.6.0,)" + } + } + } + + } + } +} +"""; + + readonly string jsonString2 = /*lang=json,strict*/ """ +{ + "version": 3, + "targets": { + "net6.0": { + "Package1/1.5.0": { + "type": "package", + "dependencies": { + "Package2": "4.5.1" + } + }, + "Package2/4.5.1": { + "type": "package" + }, + "Package3/1.0.0": { + "type": "package" + } + } + }, + "libraries": { + "Package1/1.5.0": { + "files": [ + "Package1.dll" + ], + "path": "Package1/1.5.0", + "type": "package" + }, + "Package2/4.5.1": { + "files": [ + "Package2.dll" + ], + "path": "Package2/4.5.1", + "type": "package" + }, + "Package3/1.0.0": { + "files": [ + "Package3.dll" + ], + "path": "Package3/1.0.0", + "type": "package" + } + }, + "project": { + "frameworks": { + "net6.0": { + "targetAlias": "net6.0", + "dependencies": { + "Package1": { + "target": "Package", + "version": "[1.5.0,)" + }, + "Package2": { + "target": "Package", + "version": "[4.5.1,)" + }, + "Package3": { + "suppressParent": "All", + "target": "Package", + "version": "[1.0.0,)" + } + } + }, + "netstandard2.1": { + "targetAlias": "netstandard2.1", + "dependencies": { + "Package1": { + "target": "Package", + "version": "[1.5.0,)" + }, + "Package2": { + "target": "Package", + "version": "[4.5.1,)" + }, + "Package3": { + "suppressParent": "All", + "target": "Package", + "version": "[1.0.0,)" + } + } + } + + } + } +} +"""; + [Theory] [InlineData(".NetStandard", 2, 1)] [InlineData("net", 6, 0)] @@ -55,6 +188,12 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in Name = "Package2", Version = "4.5.1", Dependencies = new Dictionary(), + }, + new NugetPackage + { + Name = "Package3", + Version = "1.0.0", + Dependencies = new Dictionary(), } }) }, @@ -68,6 +207,7 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in { ("Package1", new[]{ ("Package1", "1.5.0") }), ("Package2", new[]{ ("Package2", "4.5.1") }), + ("Package3", new[]{ ("Package3", "1.0.0") }), })); var mockAssetReader = new Mock(); mockAssetReader @@ -106,14 +246,34 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in new LockFileItem("Package2.dll") }, Dependencies = new PackageDependency[0] + }, + new LockFileTargetLibrary + { + Name = "Package3", + Version = new NuGet.Versioning.NuGetVersion("1.0.0"), + CompileTimeAssemblies = new[] + { + new LockFileItem("Package3.dll") + }, + Dependencies = new PackageDependency[0] } } } } }; }); + mockAssetReader.Setup(m => m.ReadAllText(It.IsAny())).Returns(() => + { + return "empty"; + }); - var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () => mockAssetReader.Object); + var mockJsonDoc = new Mock(); + mockJsonDoc + .Setup(m => m.Parse(It.IsAny())) + .Returns(() => JsonDocument.Parse(jsonString2) + ); + + var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () => mockAssetReader.Object, mockJsonDoc.Object ); var packages = projectAssetsFileService.GetNugetPackages(XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), false, false); var sortedPackages = new List(packages); sortedPackages.Sort(); @@ -137,7 +297,16 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in Assert.Equal(@"4.5.1", item.Version); Assert.True(item.IsDirectReference, "Package2 was expected to be a direct reference."); Assert.Empty(item.Dependencies); - }); + }, + item => + { + Assert.Equal(@"Package3", item.Name); + Assert.Equal(@"1.0.0", item.Version); + Assert.True(item.IsDirectReference, "Package3 was expected to be a direct reference."); + Assert.True(item.IsDevDependency, "Package3 was expected to be a development reference."); + Assert.Empty(item.Dependencies); + } + ); } [Theory] @@ -170,7 +339,7 @@ public void GetNugetPackages_MissingResolvedPackageVersion(string framework, int { ("Package1", new[]{ ("Package1", "1.5.0") }), })); - var mockAssetReader = new Mock(); + var mockAssetReader = new Mock(MockBehavior.Strict); mockAssetReader .Setup(m => m.Read(It.IsAny())) .Returns(() => @@ -203,8 +372,17 @@ public void GetNugetPackages_MissingResolvedPackageVersion(string framework, int } }; }); + mockAssetReader.Setup(m => m.ReadAllText(It.IsAny())).Returns(() => + { + return "empty"; + }); + var mockJsonDoc = new Mock(MockBehavior.Strict); + mockJsonDoc + .Setup(m => m.Parse(It.IsAny())) + .Returns(() => JsonDocument.Parse(jsonString2) + ); - var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () => mockAssetReader.Object); + var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () =>mockAssetReader.Object, mockJsonDoc.Object); var packages = projectAssetsFileService.GetNugetPackages(XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), false, false); var sortedPackages = new List(packages); sortedPackages.Sort(); diff --git a/CycloneDX/CycloneDX.csproj b/CycloneDX/CycloneDX.csproj index 76dc97f0..f9a26624 100644 --- a/CycloneDX/CycloneDX.csproj +++ b/CycloneDX/CycloneDX.csproj @@ -22,6 +22,10 @@ Linux + + + + diff --git a/CycloneDX/Interfaces/IAssetFileReader.cs b/CycloneDX/Interfaces/IAssetFileReader.cs index ce27b9f6..84f46d62 100644 --- a/CycloneDX/Interfaces/IAssetFileReader.cs +++ b/CycloneDX/Interfaces/IAssetFileReader.cs @@ -15,6 +15,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (c) OWASP Foundation. All Rights Reserved. +using System.IO; using NuGet.ProjectModel; namespace CycloneDX.Interfaces @@ -22,5 +23,9 @@ namespace CycloneDX.Interfaces public interface IAssetFileReader { LockFile Read(string filePath); + string ReadAllText(string filePath) + { + return File.ReadAllText(filePath); + } } } diff --git a/CycloneDX/Interfaces/IJsonDocs .cs b/CycloneDX/Interfaces/IJsonDocs .cs new file mode 100644 index 00000000..38e16305 --- /dev/null +++ b/CycloneDX/Interfaces/IJsonDocs .cs @@ -0,0 +1,27 @@ +// This file is part of CycloneDX Tool for .NET +// +// Licensed under the Apache License, Version 2.0 (the “License”); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an “AS IS” BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) OWASP Foundation. All Rights Reserved. + +using System.Text.Json; + +namespace CycloneDX.Interfaces +{ + public interface IJsonDocs + { + public JsonDocument Parse(string json); + + } +} diff --git a/CycloneDX/Program.cs b/CycloneDX/Program.cs index 29466172..bb84d247 100755 --- a/CycloneDX/Program.cs +++ b/CycloneDX/Program.cs @@ -28,6 +28,7 @@ using System.Reflection; using System.Linq; using CycloneDX.Interfaces; +using System.Text.Json; [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("CycloneDX.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("CycloneDX.IntegrationTests")] @@ -127,8 +128,9 @@ class Program { #endregion options internal static IFileSystem fileSystem = new FileSystem(); + internal static IJsonDocs jsonDoc = new JsonDocs(); internal static readonly IDotnetCommandService dotnetCommandService = new DotnetCommandService(); - internal static readonly IProjectAssetsFileService projectAssetsFileService = new ProjectAssetsFileService(fileSystem, dotnetCommandService, () => new AssetFileReader()); + internal static readonly IProjectAssetsFileService projectAssetsFileService = new ProjectAssetsFileService(fileSystem, dotnetCommandService, () => new AssetFileReader(), jsonDoc); internal static readonly IDotnetUtilsService dotnetUtilsService = new DotnetUtilsService(fileSystem, dotnetCommandService); internal static readonly IPackagesFileService packagesFileService = new PackagesFileService(fileSystem); internal static readonly IProjectFileService projectFileService = new ProjectFileService(fileSystem, dotnetUtilsService, packagesFileService, projectAssetsFileService); diff --git a/CycloneDX/Services/JsonDocs.cs b/CycloneDX/Services/JsonDocs.cs new file mode 100644 index 00000000..043fe4e8 --- /dev/null +++ b/CycloneDX/Services/JsonDocs.cs @@ -0,0 +1,32 @@ +// This file is part of CycloneDX Tool for .NET +// +// Licensed under the Apache License, Version 2.0 (the “License”); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an “AS IS” BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) OWASP Foundation. All Rights Reserved. + +using System.Text.Json; +using CycloneDX.Interfaces; + +namespace CycloneDX.Services +{ + public class JsonDocs : IJsonDocs + { + + public JsonDocument Parse(string json) + { + return JsonDocument.Parse(json); + + } + } +} diff --git a/CycloneDX/Services/ProjectAssetsFileService.cs b/CycloneDX/Services/ProjectAssetsFileService.cs index 7bb0a97c..a8280715 100644 --- a/CycloneDX/Services/ProjectAssetsFileService.cs +++ b/CycloneDX/Services/ProjectAssetsFileService.cs @@ -22,7 +22,6 @@ using System.Linq; using CycloneDX.Interfaces; using NuGet.Versioning; -using System.IO; using System.Text.Json; namespace CycloneDX.Services @@ -32,12 +31,14 @@ public class ProjectAssetsFileService : IProjectAssetsFileService private readonly IFileSystem _fileSystem; private readonly IDotnetCommandService _dotnetCommandService; private readonly Func _assetFileReaderFactory; + private readonly IJsonDocs _assetJsonObject; - public ProjectAssetsFileService(IFileSystem fileSystem, IDotnetCommandService dotnetCommandService, Func assetFileReaderFactory) + public ProjectAssetsFileService(IFileSystem fileSystem, IDotnetCommandService dotnetCommandService, Func assetFileReaderFactory, IJsonDocs assetJsonObject) { _fileSystem = fileSystem; _dotnetCommandService = dotnetCommandService; _assetFileReaderFactory = assetFileReaderFactory; + _assetJsonObject = assetJsonObject; } public HashSet GetNugetPackages(string projectFilePath, string projectAssetsFilePath, bool isTestProject, bool excludeDev) @@ -46,17 +47,16 @@ public HashSet GetNugetPackages(string projectFilePath, string pro if (_fileSystem.File.Exists(projectAssetsFilePath)) { - var jsonContent = File.ReadAllText(projectAssetsFilePath); - var assetFileObject = JsonDocument.Parse(jsonContent); + var assetFileReader = _assetFileReaderFactory(); + string jsonContent = assetFileReader.ReadAllText(projectAssetsFilePath); + JsonDocument assetFileObject = _assetJsonObject.Parse(jsonContent); // get all direct nuget dependencies of the project - var frameworksProperties = assetFileObject.RootElement.GetProperty("project").GetProperty("frameworks"); + JsonElement frameworksProperties = assetFileObject.RootElement.GetProperty("project").GetProperty("frameworks"); - var assetFileReader = _assetFileReaderFactory(); var assetsFile = assetFileReader.Read(projectAssetsFilePath); foreach (var targetRuntime in assetsFile.Targets) { - var directPackageDependencies = GetDirectPackageDependencies(targetRuntime.Name, projectFilePath); var runtimePackages = new HashSet(); foreach (var library in targetRuntime.Libraries.Where(lib => lib.Type != "project")) { @@ -67,13 +67,10 @@ public HashSet GetNugetPackages(string projectFilePath, string pro Scope = Component.ComponentScope.Required, Dependencies = new Dictionary(), // get value from project.assets.json file ( x."project"."frameworks".."dependencies".."suppressParent") - IsDevDependency = SetIsDevDependency(library.Name, targetRuntime.Name, frameworksProperties) + IsDevDependency = SetIsDevDependency(library.Name, targetRuntime.Name, frameworksProperties), + IsDirectReference = SetIsDirectReference(library.Name, targetRuntime.Name, frameworksProperties) }; - var topLevelReferenceKey = (package.Name, package.Version); - if (directPackageDependencies.Contains(topLevelReferenceKey)) - { - package.IsDirectReference = true; - } + // is this a test project dependency or only a development dependency if ( isTestProject @@ -98,38 +95,23 @@ public HashSet GetNugetPackages(string projectFilePath, string pro return packages; } - - // Future: Instead of invoking the dotnet CLI to get direct dependencies, once asset file version 3 is available through the Nuget library, - // The direct dependencies could be retrieved from the asset file json path: .project.frameworks..dependencies - private List<(string, string)> GetDirectPackageDependencies(string targetRuntime, string projectFilePath) + public bool SetIsDirectReference(string packageName, string targetRuntime, JsonElement jsonContent) { - var directPackageDependencies = new List<(string, string)>(); - var framework = TargetFrameworkToAlias(targetRuntime); - if (framework != null) + string framework = TargetFrameworkToAlias(targetRuntime); + JsonElement packageProperties; + if (jsonContent.GetProperty(framework).GetProperty("dependencies").TryGetProperty(packageName, out packageProperties)) { - var output = _dotnetCommandService.Run($"list \"{projectFilePath}\" package --framework {framework}"); - var result = output.Success ? output.StdOut : null; - if (result != null) - { - directPackageDependencies = result.Split('\r', '\n').Select(line => line.Trim()) - .Where(line => line.StartsWith(">", StringComparison.InvariantCulture)) - .Select(line => line.Split(' ', StringSplitOptions.RemoveEmptyEntries)) - .Where(parts => parts.Length == 4) - .Select(parts => (parts[1], parts[3])) - .ToList(); - } + // every direct reference has target property + return packageProperties.TryGetProperty("target", out _); } - return directPackageDependencies; + return false; } - public bool SetIsDevDependency(string packageName, string targetRuntime, JsonElement jsonContent) { string framework = TargetFrameworkToAlias(targetRuntime); JsonElement packageProperties; - var output = System.Text.Json.JsonSerializer.Serialize(jsonContent); if (jsonContent.GetProperty(framework).GetProperty("dependencies").TryGetProperty(packageName, out packageProperties)) { - output = System.Text.Json.JsonSerializer.Serialize(packageProperties); // suppressParent: exists only for development dependencies return packageProperties.TryGetProperty("suppressParent", out _); } From c331444c40fa96f55a867071de447b451c00a1ea Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 5 May 2023 14:10:05 +0200 Subject: [PATCH 16/25] avoid warning CS0414 Signed-off-by: Bert --- CycloneDX.Tests/ProjectAssetsFileServiceTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs index 6e4bf88e..b973d2b8 100644 --- a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs +++ b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs @@ -34,7 +34,7 @@ namespace CycloneDX.Tests public class ProjectAssetsFileServiceTests { - readonly string jsonString1 = /*lang=json,strict*/ """ + protected readonly string jsonString1 = /*lang=json,strict*/ """ { "version": 3, "libraries": { @@ -79,7 +79,7 @@ public class ProjectAssetsFileServiceTests } """; - readonly string jsonString2 = /*lang=json,strict*/ """ + protected readonly string jsonString2 = /*lang=json,strict*/ """ { "version": 3, "targets": { From 42a9905401ad6b57ad1230b85842759d4e27c4aa Mon Sep 17 00:00:00 2001 From: Bert Date: Sat, 6 May 2023 08:30:52 +0200 Subject: [PATCH 17/25] fix method name Signed-off-by: Bert --- CycloneDX/Services/ProjectAssetsFileService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CycloneDX/Services/ProjectAssetsFileService.cs b/CycloneDX/Services/ProjectAssetsFileService.cs index a8280715..d8eaee0a 100644 --- a/CycloneDX/Services/ProjectAssetsFileService.cs +++ b/CycloneDX/Services/ProjectAssetsFileService.cs @@ -87,7 +87,7 @@ public HashSet GetNugetPackages(string projectFilePath, string pro runtimePackages.Add(package); } - ResolveDependecyVersionRanges(runtimePackages); + ResolveDependencyVersionRanges(runtimePackages); packages.UnionWith(runtimePackages); } @@ -145,7 +145,7 @@ private string TargetFrameworkToAlias(string target) /// /// Updates all dependencies with version ranges to the version it was resolved to. /// - private static void ResolveDependecyVersionRanges(HashSet runtimePackages) + private static void ResolveDependencyVersionRanges(HashSet runtimePackages) { var runtimePackagesLookup = runtimePackages.ToLookup(x => x.Name.ToLowerInvariant()); foreach (var runtimePackage in runtimePackages) From c79f0be7792a86a8ee7618e6286abbaac7117abe Mon Sep 17 00:00:00 2001 From: Bert Date: Sat, 6 May 2023 09:19:18 +0200 Subject: [PATCH 18/25] update JsonDoc declaration Signed-off-by: Bert --- CycloneDX/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CycloneDX/Program.cs b/CycloneDX/Program.cs index bb84d247..ade50e7d 100755 --- a/CycloneDX/Program.cs +++ b/CycloneDX/Program.cs @@ -128,7 +128,7 @@ class Program { #endregion options internal static IFileSystem fileSystem = new FileSystem(); - internal static IJsonDocs jsonDoc = new JsonDocs(); + internal static readonly IJsonDocs jsonDoc = new JsonDocs(); internal static readonly IDotnetCommandService dotnetCommandService = new DotnetCommandService(); internal static readonly IProjectAssetsFileService projectAssetsFileService = new ProjectAssetsFileService(fileSystem, dotnetCommandService, () => new AssetFileReader(), jsonDoc); internal static readonly IDotnetUtilsService dotnetUtilsService = new DotnetUtilsService(fileSystem, dotnetCommandService); From cf23eecb9cc1b5606047a04063a5e201c7d7b0ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 22:56:41 +0000 Subject: [PATCH 19/25] Bump System.IO.Abstractions.TestingHelpers from 19.2.16 to 19.2.26 Bumps [System.IO.Abstractions.TestingHelpers](https://github.com/TestableIO/System.IO.Abstractions) from 19.2.16 to 19.2.26. - [Release notes](https://github.com/TestableIO/System.IO.Abstractions/releases) - [Commits](https://github.com/TestableIO/System.IO.Abstractions/compare/v19.2.16...v19.2.26) --- updated-dependencies: - dependency-name: System.IO.Abstractions.TestingHelpers dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a52a199a..0664984b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,6 +22,6 @@ - + From 811722b54e821fada3cc5bdff942bf07d9d5a403 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 08:51:54 +0000 Subject: [PATCH 20/25] Bump System.IO.Abstractions from 19.2.16 to 19.2.26 Bumps [System.IO.Abstractions](https://github.com/TestableIO/System.IO.Abstractions) from 19.2.16 to 19.2.26. - [Release notes](https://github.com/TestableIO/System.IO.Abstractions/releases) - [Commits](https://github.com/TestableIO/System.IO.Abstractions/compare/v19.2.16...v19.2.26) --- updated-dependencies: - dependency-name: System.IO.Abstractions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0664984b..78aebde4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,7 +21,7 @@ - + From 2d5c3bcc9f88d329735884ef9d7a69536b0764dd Mon Sep 17 00:00:00 2001 From: Patrick Dwyer Date: Wed, 21 Jun 2023 12:43:08 +1000 Subject: [PATCH 21/25] Feature and bugfix release --- semver.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.txt b/semver.txt index 24ba9a38..834f2629 100755 --- a/semver.txt +++ b/semver.txt @@ -1 +1 @@ -2.7.0 +2.8.0 From 273d13e24fc3c1cf0c889df580f69ec3fdbc5f81 Mon Sep 17 00:00:00 2001 From: "Kraemer, Benjamin" Date: Wed, 21 Jun 2023 09:49:56 +0200 Subject: [PATCH 22/25] Fix framework resolution when resolving dependencies Signed-off-by: Kraemer, Benjamin --- .../ProjectAssetsFileServiceTests.cs | 103 ++++++++++++++++-- .../Services/ProjectAssetsFileService.cs | 15 +-- 2 files changed, 99 insertions(+), 19 deletions(-) diff --git a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs index b973d2b8..e9ee31e7 100644 --- a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs +++ b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs @@ -73,7 +73,6 @@ public class ProjectAssetsFileServiceTests } } } - } } } @@ -84,19 +83,33 @@ public class ProjectAssetsFileServiceTests "version": 3, "targets": { "net6.0": { - "Package1/1.5.0": { - "type": "package", - "dependencies": { - "Package2": "4.5.1" + "Package1/1.5.0": { + "type": "package", + "dependencies": { + "Package2": "4.5.1" + } + }, + "Package2/4.5.1": { + "type": "package" + }, + "Package3/1.0.0": { + "type": "package" } - }, - "Package2/4.5.1": { - "type": "package" }, - "Package3/1.0.0": { - "type": "package" + "net6.0/win-x64": { + "Package1/1.5.0": { + "type": "package", + "dependencies": { + "Package2": "4.5.1" + } + }, + "Package2/4.5.1": { + "type": "package" + }, + "Package3/1.0.0": { + "type": "package" + } } - } }, "libraries": { "Package1/1.5.0": { @@ -159,7 +172,11 @@ public class ProjectAssetsFileServiceTests } } } - + } + }, + "runtimes": { + "win-x64": { + "#import": [] } } } @@ -258,6 +275,47 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in Dependencies = new PackageDependency[0] } } + }, + new LockFileTarget + { + TargetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)), + RuntimeIdentifier = "win-x64", + Libraries = new[] + { + new LockFileTargetLibrary + { + Name = "Package1", + Version = new NuGet.Versioning.NuGetVersion("1.5.0"), + CompileTimeAssemblies = new[] + { + new LockFileItem("Package1.dll") + }, + Dependencies = new[] + { + new PackageDependency("Package2", new VersionRange(minVersion: new NuGetVersion("4.5.0"), originalString:"[4.5, )")) + } + }, + new LockFileTargetLibrary + { + Name = "Package2", + Version = new NuGet.Versioning.NuGetVersion("4.5.1"), + CompileTimeAssemblies = new[] + { + new LockFileItem("Package2.dll") + }, + Dependencies = new PackageDependency[0] + }, + new LockFileTargetLibrary + { + Name = "Package3", + Version = new NuGet.Versioning.NuGetVersion("1.0.0"), + CompileTimeAssemblies = new[] + { + new LockFileItem("Package3.dll") + }, + Dependencies = new PackageDependency[0] + } + } } } }; @@ -368,6 +426,27 @@ public void GetNugetPackages_MissingResolvedPackageVersion(string framework, int } } } + }, + new LockFileTarget + { + TargetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)), + RuntimeIdentifier = "win-x64", + Libraries = new[] + { + new LockFileTargetLibrary + { + Name = "Package1", + Version = new NuGet.Versioning.NuGetVersion("1.5.0"), + CompileTimeAssemblies = new[] + { + new LockFileItem("Package1.dll") + }, + Dependencies = new[] + { + new PackageDependency("Package2", new VersionRange(minVersion: new NuGetVersion("4.5.0"), originalString:"[4.5, )")) + } + } + } } } }; diff --git a/CycloneDX/Services/ProjectAssetsFileService.cs b/CycloneDX/Services/ProjectAssetsFileService.cs index d8eaee0a..0bdb5e8c 100644 --- a/CycloneDX/Services/ProjectAssetsFileService.cs +++ b/CycloneDX/Services/ProjectAssetsFileService.cs @@ -23,6 +23,7 @@ using CycloneDX.Interfaces; using NuGet.Versioning; using System.Text.Json; +using NuGet.Frameworks; namespace CycloneDX.Services { @@ -60,6 +61,7 @@ public HashSet GetNugetPackages(string projectFilePath, string pro var runtimePackages = new HashSet(); foreach (var library in targetRuntime.Libraries.Where(lib => lib.Type != "project")) { + var targetAlias = TargetFrameworkToAlias(targetRuntime.TargetFramework); var package = new NugetPackage { Name = library.Name, @@ -67,8 +69,8 @@ public HashSet GetNugetPackages(string projectFilePath, string pro Scope = Component.ComponentScope.Required, Dependencies = new Dictionary(), // get value from project.assets.json file ( x."project"."frameworks".."dependencies".."suppressParent") - IsDevDependency = SetIsDevDependency(library.Name, targetRuntime.Name, frameworksProperties), - IsDirectReference = SetIsDirectReference(library.Name, targetRuntime.Name, frameworksProperties) + IsDevDependency = SetIsDevDependency(library.Name, targetAlias, frameworksProperties), + IsDirectReference = SetIsDirectReference(library.Name, targetAlias, frameworksProperties) }; // is this a test project dependency or only a development dependency @@ -95,9 +97,8 @@ public HashSet GetNugetPackages(string projectFilePath, string pro return packages; } - public bool SetIsDirectReference(string packageName, string targetRuntime, JsonElement jsonContent) + public bool SetIsDirectReference(string packageName, string framework, JsonElement jsonContent) { - string framework = TargetFrameworkToAlias(targetRuntime); JsonElement packageProperties; if (jsonContent.GetProperty(framework).GetProperty("dependencies").TryGetProperty(packageName, out packageProperties)) { @@ -106,9 +107,8 @@ public bool SetIsDirectReference(string packageName, string targetRuntime, JsonE } return false; } - public bool SetIsDevDependency(string packageName, string targetRuntime, JsonElement jsonContent) + public bool SetIsDevDependency(string packageName, string framework, JsonElement jsonContent) { - string framework = TargetFrameworkToAlias(targetRuntime); JsonElement packageProperties; if (jsonContent.GetProperty(framework).GetProperty("dependencies").TryGetProperty(packageName, out packageProperties)) { @@ -127,8 +127,9 @@ public bool SetIsDevDependency(string packageName, string targetRuntime, JsonEle /// netcoreapp3.1 => netcoreapp3.1 /// net6.0 => net6.0 /// - private string TargetFrameworkToAlias(string target) + private string TargetFrameworkToAlias(NuGetFramework framework) { + var target = framework.ToString(); if (!string.IsNullOrEmpty(target)) { target = target.ToLowerInvariant().TrimStart('.'); From 28037fe02c0d7df571f1952e234fb1cd4e244767 Mon Sep 17 00:00:00 2001 From: "Kraemer, Benjamin" Date: Tue, 27 Jun 2023 09:36:31 +0200 Subject: [PATCH 23/25] Added TryGetProperty also for dependencies Signed-off-by: Kraemer, Benjamin --- .../ProjectAssetsFileServiceTests.cs | 97 +++++++++++++++++++ .../Services/ProjectAssetsFileService.cs | 36 ++++--- 2 files changed, 114 insertions(+), 19 deletions(-) diff --git a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs index e9ee31e7..7b828f4e 100644 --- a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs +++ b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs @@ -480,5 +480,102 @@ public void GetNugetPackages_MissingResolvedPackageVersion(string framework, int }); }); } + + [Theory] + [InlineData(".NetStandard", 2, 1)] + [InlineData("net", 6, 0)] + public void GetNugetPackages_MissingDependencies(string framework, int frameworkMajor, int frameworkMinor) + { + var mockFileSystem = new MockFileSystem(new Dictionary + { + { XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), Helpers.GetProjectFileWithPackageReferences( + new[] { + new NugetPackage + { + Name = "Package1", + Version = "1.5.0", + } + }) + }, + { XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), new MockFileData("") + } + }); + var mockDotnetCommandsService = new Mock(); + mockDotnetCommandsService.Setup(m => m.Run(It.IsAny())) + .Returns(() => Helpers.GetDotnetListPackagesResult( + new[] + { + ("Package1", new[]{ ("Package1", "1.5.0") }), + })); + var mockAssetReader = new Mock(MockBehavior.Strict); + mockAssetReader + .Setup(m => m.Read(It.IsAny())) + .Returns(() => + { + return new LockFile + { + Targets = new[] + { + new LockFileTarget + { + TargetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)), + RuntimeIdentifier = "", + Libraries = new[] + { + new LockFileTargetLibrary + { + Name = "Package1", + Version = new NuGet.Versioning.NuGetVersion("1.5.0"), + CompileTimeAssemblies = new[] + { + new LockFileItem("Package1.dll") + } + } + } + }, + new LockFileTarget + { + TargetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)), + RuntimeIdentifier = "win-x64", + Libraries = new[] + { + new LockFileTargetLibrary + { + Name = "Package1", + Version = new NuGet.Versioning.NuGetVersion("1.5.0"), + CompileTimeAssemblies = new[] + { + new LockFileItem("Package1.dll") + } + } + } + } + } + }; + }); + mockAssetReader.Setup(m => m.ReadAllText(It.IsAny())).Returns(() => + { + return "empty"; + }); + var mockJsonDoc = new Mock(MockBehavior.Strict); + mockJsonDoc + .Setup(m => m.Parse(It.IsAny())) + .Returns(() => JsonDocument.Parse(jsonString2) + ); + + var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () =>mockAssetReader.Object, mockJsonDoc.Object); + var packages = projectAssetsFileService.GetNugetPackages(XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), false, false); + var sortedPackages = new List(packages); + sortedPackages.Sort(); + + Assert.Collection(sortedPackages, + item => + { + Assert.Equal(@"Package1", item.Name); + Assert.Equal(@"1.5.0", item.Version); + Assert.True(item.IsDirectReference, "Package1 was expected to be a direct reference."); + Assert.Empty(item.Dependencies); + }); + } } } diff --git a/CycloneDX/Services/ProjectAssetsFileService.cs b/CycloneDX/Services/ProjectAssetsFileService.cs index 0bdb5e8c..6980858f 100644 --- a/CycloneDX/Services/ProjectAssetsFileService.cs +++ b/CycloneDX/Services/ProjectAssetsFileService.cs @@ -59,9 +59,17 @@ public HashSet GetNugetPackages(string projectFilePath, string pro foreach (var targetRuntime in assetsFile.Targets) { var runtimePackages = new HashSet(); + var targetAlias = TargetFrameworkToAlias(targetRuntime.TargetFramework); + var dependenciesProperties = default(JsonElement); + var hasDependencyProperties = frameworksProperties.TryGetProperty(targetAlias, out var frameworkProperties) + && frameworkProperties.TryGetProperty("dependencies", out dependenciesProperties); + foreach (var library in targetRuntime.Libraries.Where(lib => lib.Type != "project")) { - var targetAlias = TargetFrameworkToAlias(targetRuntime.TargetFramework); + var packageProperties = default(JsonElement); + var hasPackageProperties = hasDependencyProperties + && dependenciesProperties.TryGetProperty(library.Name, out packageProperties); + var package = new NugetPackage { Name = library.Name, @@ -69,8 +77,8 @@ public HashSet GetNugetPackages(string projectFilePath, string pro Scope = Component.ComponentScope.Required, Dependencies = new Dictionary(), // get value from project.assets.json file ( x."project"."frameworks".."dependencies".."suppressParent") - IsDevDependency = SetIsDevDependency(library.Name, targetAlias, frameworksProperties), - IsDirectReference = SetIsDirectReference(library.Name, targetAlias, frameworksProperties) + IsDevDependency = hasPackageProperties && SetIsDevDependency(packageProperties), + IsDirectReference = hasPackageProperties && SetIsDirectReference(packageProperties) }; // is this a test project dependency or only a development dependency @@ -97,25 +105,15 @@ public HashSet GetNugetPackages(string projectFilePath, string pro return packages; } - public bool SetIsDirectReference(string packageName, string framework, JsonElement jsonContent) + public bool SetIsDirectReference(JsonElement jsonContent) { - JsonElement packageProperties; - if (jsonContent.GetProperty(framework).GetProperty("dependencies").TryGetProperty(packageName, out packageProperties)) - { - // every direct reference has target property - return packageProperties.TryGetProperty("target", out _); - } - return false; + // every direct reference has target property + return jsonContent.TryGetProperty("target", out _); } - public bool SetIsDevDependency(string packageName, string framework, JsonElement jsonContent) + public bool SetIsDevDependency(JsonElement jsonContent) { - JsonElement packageProperties; - if (jsonContent.GetProperty(framework).GetProperty("dependencies").TryGetProperty(packageName, out packageProperties)) - { - // suppressParent: exists only for development dependencies - return packageProperties.TryGetProperty("suppressParent", out _); - } - return false; + // suppressParent: exists only for development dependencies + return jsonContent.TryGetProperty("suppressParent", out _); } /// From 473b538be6c5c52c1bf264e87f803f044325dcce Mon Sep 17 00:00:00 2001 From: "Kraemer, Benjamin" Date: Wed, 28 Jun 2023 10:03:42 +0200 Subject: [PATCH 24/25] Improved resillience and support for .NETFramework Signed-off-by: Kraemer, Benjamin --- .../ProjectAssetsFileServiceTests.cs | 286 +++++++----------- CycloneDX/Program.cs | 4 +- .../Services/ProjectAssetsFileService.cs | 64 +--- 3 files changed, 124 insertions(+), 230 deletions(-) diff --git a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs index 7b828f4e..372babf4 100644 --- a/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs +++ b/CycloneDX.Tests/ProjectAssetsFileServiceTests.cs @@ -24,164 +24,15 @@ using Moq; using CycloneDX.Models; using CycloneDX.Services; +using NuGet.LibraryModel; using NuGet.ProjectModel; using NuGet.Packaging.Core; using NuGet.Versioning; -using System.Text.Json; namespace CycloneDX.Tests { public class ProjectAssetsFileServiceTests { - - protected readonly string jsonString1 = /*lang=json,strict*/ """ -{ - "version": 3, - "libraries": { - "Package1/1.5.0": { - "files": [ - "Package1.dll" - ], - "path": "Package1/1.5.0", - "type": "package" - }, - "Package2/4.5.1": { - "files": [ - "Package2.dll" - ], - "path": "Package2/4.5.1", - "type": "package" - } - }, - "project": { - "frameworks": { - "net6.0": { - "targetAlias": "net6.0", - "dependencies": { - "Package1": { - "target": "Package", - "version": "[1.6.0,)" - } - } - }, - "netstandard2.1": { - "targetAlias": "netstandard2.1", - "dependencies": { - "Package1": { - "target": "Package", - "version": "[1.6.0,)" - } - } - } - } - } -} -"""; - - protected readonly string jsonString2 = /*lang=json,strict*/ """ -{ - "version": 3, - "targets": { - "net6.0": { - "Package1/1.5.0": { - "type": "package", - "dependencies": { - "Package2": "4.5.1" - } - }, - "Package2/4.5.1": { - "type": "package" - }, - "Package3/1.0.0": { - "type": "package" - } - }, - "net6.0/win-x64": { - "Package1/1.5.0": { - "type": "package", - "dependencies": { - "Package2": "4.5.1" - } - }, - "Package2/4.5.1": { - "type": "package" - }, - "Package3/1.0.0": { - "type": "package" - } - } - }, - "libraries": { - "Package1/1.5.0": { - "files": [ - "Package1.dll" - ], - "path": "Package1/1.5.0", - "type": "package" - }, - "Package2/4.5.1": { - "files": [ - "Package2.dll" - ], - "path": "Package2/4.5.1", - "type": "package" - }, - "Package3/1.0.0": { - "files": [ - "Package3.dll" - ], - "path": "Package3/1.0.0", - "type": "package" - } - }, - "project": { - "frameworks": { - "net6.0": { - "targetAlias": "net6.0", - "dependencies": { - "Package1": { - "target": "Package", - "version": "[1.5.0,)" - }, - "Package2": { - "target": "Package", - "version": "[4.5.1,)" - }, - "Package3": { - "suppressParent": "All", - "target": "Package", - "version": "[1.0.0,)" - } - } - }, - "netstandard2.1": { - "targetAlias": "netstandard2.1", - "dependencies": { - "Package1": { - "target": "Package", - "version": "[1.5.0,)" - }, - "Package2": { - "target": "Package", - "version": "[4.5.1,)" - }, - "Package3": { - "suppressParent": "All", - "target": "Package", - "version": "[1.0.0,)" - } - } - } - } - }, - "runtimes": { - "win-x64": { - "#import": [] - } - } -} -"""; - [Theory] [InlineData(".NetStandard", 2, 1)] [InlineData("net", 6, 0)] @@ -211,6 +62,7 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in Name = "Package3", Version = "1.0.0", Dependencies = new Dictionary(), + IsDevDependency = true } }) }, @@ -231,13 +83,14 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in .Setup(m => m.Read(It.IsAny())) .Returns(() => { + var nuGetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)); return new LockFile { Targets = new[] { new LockFileTarget { - TargetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)), + TargetFramework = nuGetFramework, RuntimeIdentifier = "", Libraries = new[] { @@ -278,7 +131,7 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in }, new LockFileTarget { - TargetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)), + TargetFramework = nuGetFramework, RuntimeIdentifier = "win-x64", Libraries = new[] { @@ -317,6 +170,53 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in } } } + }, + PackageSpec = new PackageSpec + { + TargetFrameworks = + { + new TargetFrameworkInformation + { + FrameworkName = nuGetFramework, + TargetAlias = nuGetFramework.Framework, + Dependencies = new List + { + new() + { + SuppressParent = LibraryIncludeFlagUtils.DefaultSuppressParent, + ReferenceType = LibraryDependencyReferenceType.Direct, + LibraryRange = new LibraryRange + { + Name = "Package1", + VersionRange = VersionRange.Parse("1.5.0"), + TypeConstraint = LibraryDependencyTarget.Package + } + }, + new() + { + SuppressParent = LibraryIncludeFlagUtils.DefaultSuppressParent, + ReferenceType = LibraryDependencyReferenceType.Direct, + LibraryRange = new LibraryRange + { + Name = "Package2", + VersionRange = VersionRange.Parse("4.5.1"), + TypeConstraint = LibraryDependencyTarget.Package + } + }, + new() + { + SuppressParent = LibraryIncludeFlags.All, + ReferenceType = LibraryDependencyReferenceType.Direct, + LibraryRange = new LibraryRange + { + Name = "Package3", + VersionRange = VersionRange.Parse("1.0.0"), + TypeConstraint = LibraryDependencyTarget.Package + } + } + } + } + } } }; }); @@ -325,13 +225,7 @@ public void GetNugetPackages_PackageAsTopLevelAndTransitive(string framework, in return "empty"; }); - var mockJsonDoc = new Mock(); - mockJsonDoc - .Setup(m => m.Parse(It.IsAny())) - .Returns(() => JsonDocument.Parse(jsonString2) - ); - - var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () => mockAssetReader.Object, mockJsonDoc.Object ); + var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () => mockAssetReader.Object); var packages = projectAssetsFileService.GetNugetPackages(XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), false, false); var sortedPackages = new List(packages); sortedPackages.Sort(); @@ -402,13 +296,14 @@ public void GetNugetPackages_MissingResolvedPackageVersion(string framework, int .Setup(m => m.Read(It.IsAny())) .Returns(() => { + var nuGetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)); return new LockFile { Targets = new[] { new LockFileTarget { - TargetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)), + TargetFramework = nuGetFramework, RuntimeIdentifier = "", Libraries = new[] { @@ -429,7 +324,7 @@ public void GetNugetPackages_MissingResolvedPackageVersion(string framework, int }, new LockFileTarget { - TargetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)), + TargetFramework = nuGetFramework, RuntimeIdentifier = "win-x64", Libraries = new[] { @@ -448,6 +343,31 @@ public void GetNugetPackages_MissingResolvedPackageVersion(string framework, int } } } + }, + PackageSpec = new PackageSpec + { + TargetFrameworks = + { + new TargetFrameworkInformation + { + FrameworkName = nuGetFramework, + TargetAlias = nuGetFramework.Framework, + Dependencies = new List + { + new() + { + SuppressParent = LibraryIncludeFlagUtils.DefaultSuppressParent, + ReferenceType = LibraryDependencyReferenceType.Direct, + LibraryRange = new LibraryRange + { + Name = "Package1", + VersionRange = VersionRange.Parse("1.5.0"), + TypeConstraint = LibraryDependencyTarget.Package + } + } + } + } + } } }; }); @@ -455,13 +375,8 @@ public void GetNugetPackages_MissingResolvedPackageVersion(string framework, int { return "empty"; }); - var mockJsonDoc = new Mock(MockBehavior.Strict); - mockJsonDoc - .Setup(m => m.Parse(It.IsAny())) - .Returns(() => JsonDocument.Parse(jsonString2) - ); - var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () =>mockAssetReader.Object, mockJsonDoc.Object); + var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () => mockAssetReader.Object); var packages = projectAssetsFileService.GetNugetPackages(XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), false, false); var sortedPackages = new List(packages); sortedPackages.Sort(); @@ -512,13 +427,14 @@ public void GetNugetPackages_MissingDependencies(string framework, int framework .Setup(m => m.Read(It.IsAny())) .Returns(() => { + var nuGetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)); return new LockFile { Targets = new[] { new LockFileTarget { - TargetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)), + TargetFramework = nuGetFramework, RuntimeIdentifier = "", Libraries = new[] { @@ -535,7 +451,7 @@ public void GetNugetPackages_MissingDependencies(string framework, int framework }, new LockFileTarget { - TargetFramework = new NuGet.Frameworks.NuGetFramework(framework, new Version(frameworkMajor, frameworkMinor, 0)), + TargetFramework = nuGetFramework, RuntimeIdentifier = "win-x64", Libraries = new[] { @@ -550,6 +466,31 @@ public void GetNugetPackages_MissingDependencies(string framework, int framework } } } + }, + PackageSpec = new PackageSpec + { + TargetFrameworks = + { + new TargetFrameworkInformation + { + FrameworkName = nuGetFramework, + TargetAlias = nuGetFramework.Framework, + Dependencies = new List + { + new() + { + SuppressParent = LibraryIncludeFlagUtils.DefaultSuppressParent, + ReferenceType = LibraryDependencyReferenceType.Direct, + LibraryRange = new LibraryRange + { + Name = "Package1", + VersionRange = VersionRange.Parse("1.5.0"), + TypeConstraint = LibraryDependencyTarget.Package + } + } + } + } + } } }; }); @@ -557,13 +498,8 @@ public void GetNugetPackages_MissingDependencies(string framework, int framework { return "empty"; }); - var mockJsonDoc = new Mock(MockBehavior.Strict); - mockJsonDoc - .Setup(m => m.Parse(It.IsAny())) - .Returns(() => JsonDocument.Parse(jsonString2) - ); - var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () =>mockAssetReader.Object, mockJsonDoc.Object); + var projectAssetsFileService = new ProjectAssetsFileService(mockFileSystem, mockDotnetCommandsService.Object, () => mockAssetReader.Object); var packages = projectAssetsFileService.GetNugetPackages(XFS.Path(@"c:\SolutionPath\Project1\Project1.csproj"), XFS.Path(@"c:\SolutionPath\Project1\obj\project.assets.json"), false, false); var sortedPackages = new List(packages); sortedPackages.Sort(); diff --git a/CycloneDX/Program.cs b/CycloneDX/Program.cs index ade50e7d..29466172 100755 --- a/CycloneDX/Program.cs +++ b/CycloneDX/Program.cs @@ -28,7 +28,6 @@ using System.Reflection; using System.Linq; using CycloneDX.Interfaces; -using System.Text.Json; [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("CycloneDX.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("CycloneDX.IntegrationTests")] @@ -128,9 +127,8 @@ class Program { #endregion options internal static IFileSystem fileSystem = new FileSystem(); - internal static readonly IJsonDocs jsonDoc = new JsonDocs(); internal static readonly IDotnetCommandService dotnetCommandService = new DotnetCommandService(); - internal static readonly IProjectAssetsFileService projectAssetsFileService = new ProjectAssetsFileService(fileSystem, dotnetCommandService, () => new AssetFileReader(), jsonDoc); + internal static readonly IProjectAssetsFileService projectAssetsFileService = new ProjectAssetsFileService(fileSystem, dotnetCommandService, () => new AssetFileReader()); internal static readonly IDotnetUtilsService dotnetUtilsService = new DotnetUtilsService(fileSystem, dotnetCommandService); internal static readonly IPackagesFileService packagesFileService = new PackagesFileService(fileSystem); internal static readonly IProjectFileService projectFileService = new ProjectFileService(fileSystem, dotnetUtilsService, packagesFileService, projectAssetsFileService); diff --git a/CycloneDX/Services/ProjectAssetsFileService.cs b/CycloneDX/Services/ProjectAssetsFileService.cs index 6980858f..d21619cb 100644 --- a/CycloneDX/Services/ProjectAssetsFileService.cs +++ b/CycloneDX/Services/ProjectAssetsFileService.cs @@ -22,8 +22,8 @@ using System.Linq; using CycloneDX.Interfaces; using NuGet.Versioning; -using System.Text.Json; -using NuGet.Frameworks; +using NuGet.LibraryModel; +using NuGet.ProjectModel; namespace CycloneDX.Services { @@ -32,14 +32,12 @@ public class ProjectAssetsFileService : IProjectAssetsFileService private readonly IFileSystem _fileSystem; private readonly IDotnetCommandService _dotnetCommandService; private readonly Func _assetFileReaderFactory; - private readonly IJsonDocs _assetJsonObject; - public ProjectAssetsFileService(IFileSystem fileSystem, IDotnetCommandService dotnetCommandService, Func assetFileReaderFactory, IJsonDocs assetJsonObject) + public ProjectAssetsFileService(IFileSystem fileSystem, IDotnetCommandService dotnetCommandService, Func assetFileReaderFactory) { _fileSystem = fileSystem; _dotnetCommandService = dotnetCommandService; _assetFileReaderFactory = assetFileReaderFactory; - _assetJsonObject = assetJsonObject; } public HashSet GetNugetPackages(string projectFilePath, string projectAssetsFilePath, bool isTestProject, bool excludeDev) @@ -49,36 +47,25 @@ public HashSet GetNugetPackages(string projectFilePath, string pro if (_fileSystem.File.Exists(projectAssetsFilePath)) { var assetFileReader = _assetFileReaderFactory(); - string jsonContent = assetFileReader.ReadAllText(projectAssetsFilePath); - JsonDocument assetFileObject = _assetJsonObject.Parse(jsonContent); - // get all direct nuget dependencies of the project - JsonElement frameworksProperties = assetFileObject.RootElement.GetProperty("project").GetProperty("frameworks"); - var assetsFile = assetFileReader.Read(projectAssetsFilePath); foreach (var targetRuntime in assetsFile.Targets) { var runtimePackages = new HashSet(); - var targetAlias = TargetFrameworkToAlias(targetRuntime.TargetFramework); - var dependenciesProperties = default(JsonElement); - var hasDependencyProperties = frameworksProperties.TryGetProperty(targetAlias, out var frameworkProperties) - && frameworkProperties.TryGetProperty("dependencies", out dependenciesProperties); + var targetFramework = assetsFile.PackageSpec.GetTargetFramework(targetRuntime.TargetFramework); + var dependencies = targetFramework.Dependencies; foreach (var library in targetRuntime.Libraries.Where(lib => lib.Type != "project")) { - var packageProperties = default(JsonElement); - var hasPackageProperties = hasDependencyProperties - && dependenciesProperties.TryGetProperty(library.Name, out packageProperties); - + var libs = dependencies.FirstOrDefault(ld => ld.Name.Equals(library.Name)); var package = new NugetPackage { Name = library.Name, Version = library.Version.ToNormalizedString(), Scope = Component.ComponentScope.Required, Dependencies = new Dictionary(), - // get value from project.assets.json file ( x."project"."frameworks".."dependencies".."suppressParent") - IsDevDependency = hasPackageProperties && SetIsDevDependency(packageProperties), - IsDirectReference = hasPackageProperties && SetIsDirectReference(packageProperties) + IsDevDependency = SetIsDevDependency(libs), + IsDirectReference = SetIsDirectReference(libs) }; // is this a test project dependency or only a development dependency @@ -105,40 +92,13 @@ public HashSet GetNugetPackages(string projectFilePath, string pro return packages; } - public bool SetIsDirectReference(JsonElement jsonContent) + public bool SetIsDirectReference(LibraryDependency dependency) { - // every direct reference has target property - return jsonContent.TryGetProperty("target", out _); + return dependency?.ReferenceType == LibraryDependencyReferenceType.Direct; } - public bool SetIsDevDependency(JsonElement jsonContent) + public bool SetIsDevDependency(LibraryDependency dependency) { - // suppressParent: exists only for development dependencies - return jsonContent.TryGetProperty("suppressParent", out _); - } - - /// - /// Converts an asset file's target framework value into a csproj target framework value. - /// - /// Examples: - /// .NetStandard,Version=V3.1 => netstandard3.1 - /// .NetCoreApp,Version=V3.1 => netcoreapp3.1 - /// netcoreapp3.1 => netcoreapp3.1 - /// net6.0 => net6.0 - /// - private string TargetFrameworkToAlias(NuGetFramework framework) - { - var target = framework.ToString(); - if (!string.IsNullOrEmpty(target)) - { - target = target.ToLowerInvariant().TrimStart('.'); - var targetParts = target.Split(",version=v"); - if (targetParts.Length == 2) - { - return string.Join("", targetParts); - } - return targetParts[0]; - } - return null; + return dependency != null && dependency.SuppressParent != LibraryIncludeFlagUtils.DefaultSuppressParent; } /// From 47967d7e7eccb21e0b6779fb076376a5db5b7b9c Mon Sep 17 00:00:00 2001 From: Patrick Dwyer Date: Tue, 11 Jul 2023 17:34:25 +1000 Subject: [PATCH 25/25] Bug fix release - Fix framework resolution when resolving dependencies --- semver.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.txt b/semver.txt index 834f2629..dbe59006 100755 --- a/semver.txt +++ b/semver.txt @@ -1 +1 @@ -2.8.0 +2.8.1