From b2b73f3921b5a8af90e364e659dd0b9a93704473 Mon Sep 17 00:00:00 2001 From: Alexej Kowalew <616b2f@gmail.com> Date: Fri, 23 Aug 2024 20:40:16 +0200 Subject: [PATCH 1/6] feat(nuget): add repository_url support for nuget purl --- lua/mason-core/installer/managers/nuget.lua | 14 +++++++++++--- .../installer/registry/providers/nuget.lua | 8 +++++++- tests/fixtures/purl-test-suite-data.json | 6 +++--- .../installer/registry/providers/nuget_spec.lua | 6 ++++-- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/lua/mason-core/installer/managers/nuget.lua b/lua/mason-core/installer/managers/nuget.lua index 9f1badc7a..5e9d4d93d 100644 --- a/lua/mason-core/installer/managers/nuget.lua +++ b/lua/mason-core/installer/managers/nuget.lua @@ -9,18 +9,26 @@ local M = {} ---@param package string ---@param version string ---@nodiscard -function M.install(package, version) +function M.install(package, version, repository_url) log.fmt_debug("nuget: install %s %s", package, version) local ctx = installer.context() ctx.stdio_sink.stdout(("Installing nuget package %s@%s…\n"):format(package, version)) - return ctx.spawn.dotnet { + + local args = { "tool", "update", "--tool-path", ".", { "--version", version }, - package, } + + if repository_url then + table.insert(args, { "--add-source", repository_url }) + end + + table.insert(args, package) + + return ctx.spawn.dotnet(args) end ---@param bin string diff --git a/lua/mason-core/installer/registry/providers/nuget.lua b/lua/mason-core/installer/registry/providers/nuget.lua index 370c7b959..d87b909e8 100644 --- a/lua/mason-core/installer/registry/providers/nuget.lua +++ b/lua/mason-core/installer/registry/providers/nuget.lua @@ -1,14 +1,20 @@ local Result = require "mason-core.result" +local _ = require "mason-core.functional" local M = {} ---@param source RegistryPackageSource ---@param purl Purl function M.parse(source, purl) + + local repository_url = _.path({ "qualifiers", "repository_url" }, purl) + ---@class ParsedNugetSource : ParsedPackageSource + ---@field repository_url string Custom repository URL to pull from local parsed_source = { package = purl.name, version = purl.version, + repository_url = repository_url } return Result.success(parsed_source) @@ -19,7 +25,7 @@ end ---@param source ParsedNugetSource function M.install(ctx, source) local nuget = require "mason-core.installer.managers.nuget" - return nuget.install(source.package, source.version) + return nuget.install(source.package, source.version, source.repository_url) end ---@async diff --git a/tests/fixtures/purl-test-suite-data.json b/tests/fixtures/purl-test-suite-data.json index 3856ab4ee..3cfb2e08d 100644 --- a/tests/fixtures/purl-test-suite-data.json +++ b/tests/fixtures/purl-test-suite-data.json @@ -157,13 +157,13 @@ }, { "description": "nuget names are case sensitive", - "purl": "pkg:Nuget/EnterpriseLibrary.Common@6.0.1304", - "canonical_purl": "pkg:nuget/EnterpriseLibrary.Common@6.0.1304", + "purl": "pkg:Nuget/EnterpriseLibrary.Common@6.0.1304?repository_url=https://api.nuget.org/v3/index.json", + "canonical_purl": "pkg:nuget/EnterpriseLibrary.Common@6.0.1304?repository_url=https://api.nuget.org/v3/index.json", "type": "nuget", "namespace": null, "name": "EnterpriseLibrary.Common", "version": "6.0.1304", - "qualifiers": null, + "qualifiers": { "repository_url": "https://api.nuget.org/v3/index.json" }, "subpath": null, "is_invalid": false }, diff --git a/tests/mason-core/installer/registry/providers/nuget_spec.lua b/tests/mason-core/installer/registry/providers/nuget_spec.lua index 2437d8de9..226a99471 100644 --- a/tests/mason-core/installer/registry/providers/nuget_spec.lua +++ b/tests/mason-core/installer/registry/providers/nuget_spec.lua @@ -6,7 +6,7 @@ local stub = require "luassert.stub" ---@param overrides Purl local function purl(overrides) - local purl = Purl.parse("pkg:nuget/package@2.2.0"):get_or_throw() + local purl = Purl.parse("pkg:nuget/package@2.2.0?repository_url=https://api.nuget.org/v3/index.json"):get_or_throw() if not overrides then return purl end @@ -19,6 +19,7 @@ describe("nuget provider :: parsing", function() Result.success { package = "package", version = "2.2.0", + repository_url = "https://api.nuget.org/v3/index.json" }, nuget.parse({}, purl()) ) @@ -35,11 +36,12 @@ describe("nuget provider :: installing", function() return nuget.install(ctx, { package = "package", version = "1.5.0", + repository_url = "https://api.nuget.org/v3/index.json" }) end) assert.is_true(result:is_success()) assert.spy(manager.install).was_called(1) - assert.spy(manager.install).was_called_with("package", "1.5.0") + assert.spy(manager.install).was_called_with("package", "1.5.0", "https://api.nuget.org/v3/index.json") end) end) From c0cec2f5b09b7cf5931326473158e176619a427a Mon Sep 17 00:00:00 2001 From: Alexej Kowalew <616b2f@gmail.com> Date: Fri, 23 Aug 2024 23:38:43 +0200 Subject: [PATCH 2/6] feat(nuget): fetch plain nupkg files and extract them --- lua/mason-core/installer/managers/nuget.lua | 48 ++++++++++++++----- lua/mason-core/installer/managers/std.lua | 1 + .../installer/registry/providers/nuget.lua | 4 ++ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/lua/mason-core/installer/managers/nuget.lua b/lua/mason-core/installer/managers/nuget.lua index 5e9d4d93d..b72aac590 100644 --- a/lua/mason-core/installer/managers/nuget.lua +++ b/lua/mason-core/installer/managers/nuget.lua @@ -2,6 +2,8 @@ local Result = require "mason-core.result" local installer = require "mason-core.installer" local log = require "mason-core.log" local platform = require "mason-core.platform" +local fetch = require "mason-core.fetch" +local common = require "mason-core.installer.managers.common" local M = {} @@ -12,23 +14,47 @@ local M = {} function M.install(package, version, repository_url) log.fmt_debug("nuget: install %s %s", package, version) local ctx = installer.context() + + local index_file = M.fetch_nuget_index_file(repository_url):get_or_throw() + + assert(index_file, "nuget index file could not be retrieved") + + local resource = vim.iter(index_file.resources) + :find(function (v) + return v['@type'] == 'PackageBaseAddress/3.0.0' + end) + + assert(resource, "could not get PackageBaseAddress resource from nuget index file") + + local nupkg_download_url = string.format("%s%s/%s/%s.%s.nupkg", + resource["@id"], + package:lower(), + version, + package:lower(), + version) + ctx.stdio_sink.stdout(("Installing nuget package %s@%s…\n"):format(package, version)) - local args = { - "tool", - "update", - "--tool-path", - ".", - { "--version", version }, + local download_item = { + download_url = nupkg_download_url, + out_file = string.format("%s-%s.nupkg", package, version) } - if repository_url then - table.insert(args, { "--add-source", repository_url }) - end + return common.download_files(ctx, { download_item }) +end - table.insert(args, package) +---@alias NugetIndexResource { '@id': string, '@type': string} +---@alias NugetIndexFile { version: string, resources: NugetIndexResource[]} - return ctx.spawn.dotnet(args) +---@async +---@param repository_url string +---@return Result # Result +function M.fetch_nuget_index_file(repository_url) + return fetch(repository_url, { + headers = { + Accept = "application/json", + }, + }):map_catching(vim.json.decode) end ---@param bin string diff --git a/lua/mason-core/installer/managers/std.lua b/lua/mason-core/installer/managers/std.lua index 6e1a0d9ef..ec85de23a 100644 --- a/lua/mason-core/installer/managers/std.lua +++ b/lua/mason-core/installer/managers/std.lua @@ -224,6 +224,7 @@ local unpack_by_filename = _.cond { { _.matches "%.tar%.zst$", untar_zst }, { _.matches "%.zip$", unzip }, { _.matches "%.vsix$", unzip }, + { _.matches "%.nupkg$", unzip }, { _.matches "%.gz$", gunzip }, { _.T, _.compose(Result.success, _.format "%q doesn't need unpacking.") }, } diff --git a/lua/mason-core/installer/registry/providers/nuget.lua b/lua/mason-core/installer/registry/providers/nuget.lua index d87b909e8..b98ec5423 100644 --- a/lua/mason-core/installer/registry/providers/nuget.lua +++ b/lua/mason-core/installer/registry/providers/nuget.lua @@ -9,6 +9,10 @@ function M.parse(source, purl) local repository_url = _.path({ "qualifiers", "repository_url" }, purl) + if not repository_url then + repository_url = "https://api.nuget.org/v3/index.json" + end + ---@class ParsedNugetSource : ParsedPackageSource ---@field repository_url string Custom repository URL to pull from local parsed_source = { From 6386459f2bdf4219d88a1430cc066c3b7ba3a9fd Mon Sep 17 00:00:00 2001 From: Alexej Kowalew <616b2f@gmail.com> Date: Tue, 3 Sep 2024 15:30:46 +0200 Subject: [PATCH 3/6] feat(nuget): implement nuget tool check --- lua/mason-core/installer/managers/nuget.lua | 39 ++++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/lua/mason-core/installer/managers/nuget.lua b/lua/mason-core/installer/managers/nuget.lua index b72aac590..89c6cdea0 100644 --- a/lua/mason-core/installer/managers/nuget.lua +++ b/lua/mason-core/installer/managers/nuget.lua @@ -15,7 +15,7 @@ function M.install(package, version, repository_url) log.fmt_debug("nuget: install %s %s", package, version) local ctx = installer.context() - local index_file = M.fetch_nuget_index_file(repository_url):get_or_throw() + local index_file = M.fetch_nuget_endpoint(repository_url):get_or_throw() assert(index_file, "nuget index file could not be retrieved") @@ -26,13 +26,31 @@ function M.install(package, version, repository_url) assert(resource, "could not get PackageBaseAddress resource from nuget index file") + local package_base_address = resource["@id"] + local package_lowercase = package:lower() + local nupkg_download_url = string.format("%s%s/%s/%s.%s.nupkg", - resource["@id"], - package:lower(), + package_base_address, + package_lowercase, version, - package:lower(), + package_lowercase, version) + local nuspec_url = string.format("%s%s/%s/%s.nuspec", + package_base_address, + package_lowercase, + version, + package_lowercase) + + assert(nuspec_url, "nuspec url should be set") + + local nuspec_file = M.fetch_nuget_endpoint_xml(nuspec_url):get_or_throw() + + if string.match(nuspec_file, "") then + -- print(vim.inspect(nuspec_file)) + print(package_lowercase .. ": " .. "It's a tool!") + end + ctx.stdio_sink.stdout(("Installing nuget package %s@%s…\n"):format(package, version)) local download_item = { @@ -49,7 +67,7 @@ end ---@async ---@param repository_url string ---@return Result # Result -function M.fetch_nuget_index_file(repository_url) +function M.fetch_nuget_endpoint(repository_url) return fetch(repository_url, { headers = { Accept = "application/json", @@ -57,6 +75,17 @@ function M.fetch_nuget_index_file(repository_url) }):map_catching(vim.json.decode) end +---@async +---@param repository_url string +---@return Result +function M.fetch_nuget_endpoint_xml(repository_url) + return fetch(repository_url, { + headers = { + Accept = "application/xml", + }, + }) +end + ---@param bin string function M.bin_path(bin) return Result.pcall(platform.when, { From b2d5fcd328658c06e0d062d473f53063d4a9e21c Mon Sep 17 00:00:00 2001 From: Alexej Kowalew <616b2f@gmail.com> Date: Tue, 3 Sep 2024 22:51:32 +0200 Subject: [PATCH 4/6] feat(nuget): handle regular nuget and nuget tool properly --- lua/mason-core/installer/managers/nuget.lua | 55 +++++++++++++++------ 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/lua/mason-core/installer/managers/nuget.lua b/lua/mason-core/installer/managers/nuget.lua index 89c6cdea0..5f0649bf3 100644 --- a/lua/mason-core/installer/managers/nuget.lua +++ b/lua/mason-core/installer/managers/nuget.lua @@ -29,13 +29,6 @@ function M.install(package, version, repository_url) local package_base_address = resource["@id"] local package_lowercase = package:lower() - local nupkg_download_url = string.format("%s%s/%s/%s.%s.nupkg", - package_base_address, - package_lowercase, - version, - package_lowercase, - version) - local nuspec_url = string.format("%s%s/%s/%s.nuspec", package_base_address, package_lowercase, @@ -46,19 +39,51 @@ function M.install(package, version, repository_url) local nuspec_file = M.fetch_nuget_endpoint_xml(nuspec_url):get_or_throw() - if string.match(nuspec_file, "") then - -- print(vim.inspect(nuspec_file)) - print(package_lowercase .. ": " .. "It's a tool!") + ctx.stdio_sink.stdout(("Installing nuget package %s@%s…\n"):format(package, version)) + + local is_dotnet_tool = string.match(nuspec_file, "") + if is_dotnet_tool then + return M.install_dotnet_tool(package, version, repository_url) + else + local nupkg_download_url = string.format("%s%s/%s/%s.%s.nupkg", + package_base_address, + package_lowercase, + version, + package_lowercase, + version) + + local download_item = { + download_url = nupkg_download_url, + out_file = string.format("%s-%s.nupkg", package, version) + } + + return common.download_files(ctx, { download_item }) end +end - ctx.stdio_sink.stdout(("Installing nuget package %s@%s…\n"):format(package, version)) +---@async +---@param package string +---@param version string +---@param repository_url string +---@nodiscard +function M.install_dotnet_tool(package, version, repository_url) + local ctx = installer.context() - local download_item = { - download_url = nupkg_download_url, - out_file = string.format("%s-%s.nupkg", package, version) + local args = { + "tool", + "update", + "--tool-path", + ".", + { "--version", version }, } - return common.download_files(ctx, { download_item }) + if repository_url then + table.insert(args, { "--add-source", repository_url }) + end + + table.insert(args, package) + + return ctx.spawn.dotnet(args) end ---@alias NugetIndexResource { '@id': string, '@type': string} From 1e7f2672b18dc2c5c972617476f9d483a94727bf Mon Sep 17 00:00:00 2001 From: Alexej Kowalew <616b2f@gmail.com> Date: Tue, 3 Sep 2024 23:58:13 +0200 Subject: [PATCH 5/6] refactor(nuget): add download handling for source --- lua/mason-core/installer/managers/nuget.lua | 69 +--------------- .../installer/registry/providers/nuget.lua | 78 +++++++++++++++---- 2 files changed, 64 insertions(+), 83 deletions(-) diff --git a/lua/mason-core/installer/managers/nuget.lua b/lua/mason-core/installer/managers/nuget.lua index 5f0649bf3..820311c13 100644 --- a/lua/mason-core/installer/managers/nuget.lua +++ b/lua/mason-core/installer/managers/nuget.lua @@ -1,72 +1,16 @@ local Result = require "mason-core.result" local installer = require "mason-core.installer" -local log = require "mason-core.log" local platform = require "mason-core.platform" local fetch = require "mason-core.fetch" -local common = require "mason-core.installer.managers.common" local M = {} ----@async ----@param package string ----@param version string ----@nodiscard -function M.install(package, version, repository_url) - log.fmt_debug("nuget: install %s %s", package, version) - local ctx = installer.context() - - local index_file = M.fetch_nuget_endpoint(repository_url):get_or_throw() - - assert(index_file, "nuget index file could not be retrieved") - - local resource = vim.iter(index_file.resources) - :find(function (v) - return v['@type'] == 'PackageBaseAddress/3.0.0' - end) - - assert(resource, "could not get PackageBaseAddress resource from nuget index file") - - local package_base_address = resource["@id"] - local package_lowercase = package:lower() - - local nuspec_url = string.format("%s%s/%s/%s.nuspec", - package_base_address, - package_lowercase, - version, - package_lowercase) - - assert(nuspec_url, "nuspec url should be set") - - local nuspec_file = M.fetch_nuget_endpoint_xml(nuspec_url):get_or_throw() - - ctx.stdio_sink.stdout(("Installing nuget package %s@%s…\n"):format(package, version)) - - local is_dotnet_tool = string.match(nuspec_file, "") - if is_dotnet_tool then - return M.install_dotnet_tool(package, version, repository_url) - else - local nupkg_download_url = string.format("%s%s/%s/%s.%s.nupkg", - package_base_address, - package_lowercase, - version, - package_lowercase, - version) - - local download_item = { - download_url = nupkg_download_url, - out_file = string.format("%s-%s.nupkg", package, version) - } - - return common.download_files(ctx, { download_item }) - end -end - ---@async ---@param package string ---@param version string ---@param repository_url string ---@nodiscard -function M.install_dotnet_tool(package, version, repository_url) +function M.install(package, version, repository_url) local ctx = installer.context() local args = { @@ -100,17 +44,6 @@ function M.fetch_nuget_endpoint(repository_url) }):map_catching(vim.json.decode) end ----@async ----@param repository_url string ----@return Result -function M.fetch_nuget_endpoint_xml(repository_url) - return fetch(repository_url, { - headers = { - Accept = "application/xml", - }, - }) -end - ---@param bin string function M.bin_path(bin) return Result.pcall(platform.when, { diff --git a/lua/mason-core/installer/registry/providers/nuget.lua b/lua/mason-core/installer/registry/providers/nuget.lua index b98ec5423..04d489cb1 100644 --- a/lua/mason-core/installer/registry/providers/nuget.lua +++ b/lua/mason-core/installer/registry/providers/nuget.lua @@ -1,35 +1,83 @@ local Result = require "mason-core.result" +local common = require "mason-core.installer.managers.common" +local util = require "mason-core.installer.registry.util" +local expr = require "mason-core.installer.registry.expr" +local nuget = require "mason-core.installer.managers.nuget" local _ = require "mason-core.functional" local M = {} ----@param source RegistryPackageSource +---@class NugetPackageSource : RegistryPackageSource +---@field download FileDownloadSpec + +---@param source NugetPackageSource ---@param purl Purl function M.parse(source, purl) + return Result.try(function (try) + local repository_url = _.path({ "qualifiers", "repository_url" }, purl) - local repository_url = _.path({ "qualifiers", "repository_url" }, purl) + local download_item = nil + if source.download then - if not repository_url then - repository_url = "https://api.nuget.org/v3/index.json" - end + if not repository_url then + -- if not set we need to provide repository url because we need it for + -- download url discovery + repository_url = "https://api.nuget.org/v3/index.json" + end + + local index_file = try(nuget.fetch_nuget_endpoint(repository_url)) + + local resource = vim.iter(index_file.resources) + :find(function (v) + return v['@type'] == 'PackageBaseAddress/3.0.0' + end) + + assert(resource, "could not get PackageBaseAddress resource from nuget index file") + + local package_base_address = resource["@id"] + local package_lowercase = purl.name:lower() - ---@class ParsedNugetSource : ParsedPackageSource - ---@field repository_url string Custom repository URL to pull from - local parsed_source = { - package = purl.name, - version = purl.version, - repository_url = repository_url - } + local nupkg_download_url = string.format("%s%s/%s/%s.%s.nupkg", + package_base_address, + package_lowercase, + purl.version, + package_lowercase, + purl.version) - return Result.success(parsed_source) + local expr_ctx = { version = purl.version } + + ---@type FileDownloadSpec + local download_spec = try(util.coalesce_by_target(try(expr.tbl_interpolate(source.download, expr_ctx)), {})) + + download_item = { + download_url = nupkg_download_url, + out_file = download_spec.file + } + end + + ---@class ParsedNugetSource : ParsedPackageSource + ---@field download? DownloadItem + ---@field repository_url string Custom repository URL to pull from + local parsed_source = { + package = purl.name, + version = purl.version, + download = download_item, + repository_url = repository_url + } + + return parsed_source + end) end ---@async ---@param ctx InstallContext ---@param source ParsedNugetSource function M.install(ctx, source) - local nuget = require "mason-core.installer.managers.nuget" - return nuget.install(source.package, source.version, source.repository_url) + if source.download then + return common.download_files(ctx, {source.download}) + else + return nuget.install(source.package, source.version, source.repository_url) + end end ---@async From b37ba51121ff381c58db8de682b565b35e50990c Mon Sep 17 00:00:00 2001 From: Alexej Kowalew <616b2f@gmail.com> Date: Wed, 4 Sep 2024 10:46:18 +0200 Subject: [PATCH 6/6] fix(nuget): unit tests --- lua/mason-core/installer/managers/nuget.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/mason-core/installer/managers/nuget.lua b/lua/mason-core/installer/managers/nuget.lua index 820311c13..750d7044f 100644 --- a/lua/mason-core/installer/managers/nuget.lua +++ b/lua/mason-core/installer/managers/nuget.lua @@ -1,5 +1,6 @@ local Result = require "mason-core.result" local installer = require "mason-core.installer" +local log = require "mason-core.log" local platform = require "mason-core.platform" local fetch = require "mason-core.fetch" @@ -11,8 +12,9 @@ local M = {} ---@param repository_url string ---@nodiscard function M.install(package, version, repository_url) + log.fmt_debug("nuget: install %s %s", package, version) local ctx = installer.context() - + ctx.stdio_sink.stdout(("Installing nuget package %s@%s…\n"):format(package, version)) local args = { "tool", "update",