From ec7501e103d78733fea65a31d38a0c5fa83e94fc Mon Sep 17 00:00:00 2001 From: William Boman Date: Wed, 11 Oct 2023 15:33:10 +0200 Subject: [PATCH] refactor(receipt): change receipt structure and remove old builder APIs (#1521) --- lua/mason-core/installer/init.lua | 6 +- lua/mason-core/installer/linker.lua | 6 +- lua/mason-core/installer/registry/init.lua | 3 +- lua/mason-core/package/init.lua | 10 +- lua/mason-core/receipt.lua | 124 +++++------------- lua/mason/ui/instance.lua | 2 +- tests/fixtures/receipts/1.0.json | 23 ++++ tests/fixtures/receipts/1.1.json | 27 ++++ tests/fixtures/receipts/1.2.json | 19 +++ tests/mason-core/installer/installer_spec.lua | 5 +- tests/mason-core/receipt_spec.lua | 86 ++++++++++++ 11 files changed, 207 insertions(+), 104 deletions(-) create mode 100644 tests/fixtures/receipts/1.0.json create mode 100644 tests/fixtures/receipts/1.1.json create mode 100644 tests/fixtures/receipts/1.2.json create mode 100644 tests/mason-core/receipt_spec.lua diff --git a/lua/mason-core/installer/init.lua b/lua/mason-core/installer/init.lua index 961c5c47b..45bba46b3 100644 --- a/lua/mason-core/installer/init.lua +++ b/lua/mason-core/installer/init.lua @@ -37,11 +37,7 @@ end local function build_receipt(context) return Result.pcall(function() log.fmt_debug("Building receipt for %s", context.package) - return context.receipt - :with_name(context.package.name) - :with_schema_version("1.1") - :with_completion_time(vim.loop.gettimeofday()) - :build() + return context.receipt:with_name(context.package.name):with_completion_time(vim.loop.gettimeofday()):build() end) end diff --git a/lua/mason-core/installer/linker.lua b/lua/mason-core/installer/linker.lua index 7a1098fb6..83b1f4a51 100644 --- a/lua/mason-core/installer/linker.lua +++ b/lua/mason-core/installer/linker.lua @@ -21,12 +21,12 @@ local LinkContext = { ---@param link_context LinkContext local function unlink(receipt, link_context) return Result.pcall(function() - local links = receipt.links[link_context.type] + local links = receipt:get_links()[link_context.type] if not links then return end for linked_file in pairs(links) do - if receipt.schema_version == "1.0" and link_context == LinkContext.BIN and platform.is.win then + if receipt:get_schema_version() == "1.0" and link_context == LinkContext.BIN and platform.is.win then linked_file = linked_file .. ".cmd" end local share_path = link_context.prefix(linked_file) @@ -39,7 +39,7 @@ end ---@param receipt InstallReceipt ---@nodiscard function M.unlink(pkg, receipt) - log.fmt_debug("Unlinking %s", pkg, receipt.links) + log.fmt_debug("Unlinking %s", pkg, receipt:get_links()) return Result.try(function(try) try(unlink(receipt, LinkContext.BIN)) try(unlink(receipt, LinkContext.SHARE)) diff --git a/lua/mason-core/installer/registry/init.lua b/lua/mason-core/installer/registry/init.lua index 2b656217b..6a88be747 100644 --- a/lua/mason-core/installer/registry/init.lua +++ b/lua/mason-core/installer/registry/init.lua @@ -10,6 +10,7 @@ local util = require "mason-core.installer.registry.util" local M = {} +---@type table M.SCHEMA_CAP = _.set_of { "registry+v1", } @@ -203,7 +204,7 @@ function M.compile(spec, opts) try(link.opt(ctx, spec, parsed.purl, parsed.source)) end - ctx.receipt:with_primary_source { + ctx.receipt:with_source { type = ctx.package.spec.schema, id = Purl.compile(parsed.purl), } diff --git a/lua/mason-core/package/init.lua b/lua/mason-core/package/init.lua index ec526984a..1b3966704 100644 --- a/lua/mason-core/package/init.lua +++ b/lua/mason-core/package/init.lua @@ -68,8 +68,11 @@ local PackageMt = { __index = Package } ---@field since string ---@field message string +---@alias RegistryPackageSpecSchema +--- | '"registry+v1"' + ---@class RegistryPackageSpec ----@field schema '"registry+v1"' +---@field schema RegistryPackageSpecSchema ---@field name string ---@field description string ---@field homepage string @@ -231,8 +234,9 @@ function Package:get_installed_version() :and_then( ---@param receipt InstallReceipt function(receipt) - if receipt.primary_source.id then - return Purl.parse(receipt.primary_source.id):map(_.prop "version"):ok() + local source = receipt:get_source() + if source.id then + return Purl.parse(source.id):map(_.prop "version"):ok() else return Optional.empty() end diff --git a/lua/mason-core/receipt.lua b/lua/mason-core/receipt.lua index 4beeb6aae..d9fe9d883 100644 --- a/lua/mason-core/receipt.lua +++ b/lua/mason-core/receipt.lua @@ -3,36 +3,20 @@ local M = {} ---@alias InstallReceiptSchemaVersion ---| '"1.0"' ---| '"1.1"' +---| '"1.2"' ----@alias InstallReceiptSourceType ----| '"npm"' ----| '"pip3"' ----| '"gem"' ----| '"go"' ----| '"cargo"' ----| '"opam"' ----| '"dotnet"' ----| '"unmanaged"' ----| '"system"' ----| '"jdtls"' ----| '"git"' ----| '"github_tag"' ----| '"github_release"' ----| '"github_release_file"' - ----@alias InstallReceiptSource {type: InstallReceiptSourceType} +---@alias InstallReceiptSource {type: RegistryPackageSpecSchema, id: string} ---@class InstallReceiptLinks ---@field bin? table ---@field share? table ---@field opt? table ----@class InstallReceipt : { primary_source: T } +---@class InstallReceipt ---@field public name string ---@field public schema_version InstallReceiptSchemaVersion ---@field public metrics {start_time:integer, completion_time:integer} ----@field public primary_source InstallReceiptSource ----@field public secondary_sources InstallReceiptSource[] +---@field public source InstallReceiptSource ---@field public links InstallReceiptLinks local InstallReceipt = {} InstallReceipt.__index = InstallReceipt @@ -45,6 +29,32 @@ function InstallReceipt.from_json(json) return InstallReceipt.new(json) end +function InstallReceipt:get_name() + return self.name +end + +function InstallReceipt:get_schema_version() + return self.schema_version +end + +---@param version string +function InstallReceipt:is_schema_min(version) + local semver = require "mason-vendor.semver" + return semver(self.schema_version) >= semver(version) +end + +---@return InstallReceiptSource +function InstallReceipt:get_source() + if self:is_schema_min "1.2" then + return self.source + end + return self.primary_source --[[@as InstallReceiptSource]] +end + +function InstallReceipt:get_links() + return self.links +end + ---@async ---@param cwd string function InstallReceipt:write(cwd) @@ -54,15 +64,12 @@ function InstallReceipt:write(cwd) end ---@class InstallReceiptBuilder ----@field private secondary_sources InstallReceiptSource[] ----@field private links InstallReceiptLinks ----@field private epoch_time number +---@field links InstallReceiptLinks local InstallReceiptBuilder = {} InstallReceiptBuilder.__index = InstallReceiptBuilder function InstallReceiptBuilder.new() return setmetatable({ - secondary_sources = {}, links = { bin = vim.empty_dict(), share = vim.empty_dict(), @@ -77,21 +84,9 @@ function InstallReceiptBuilder:with_name(name) return self end ----@param version InstallReceiptSchemaVersion -function InstallReceiptBuilder:with_schema_version(version) - self.schema_version = version - return self -end - ---@param source InstallReceiptSource -function InstallReceiptBuilder:with_primary_source(source) - self.primary_source = source - return self -end - ----@param source InstallReceiptSource -function InstallReceiptBuilder:with_secondary_source(source) - table.insert(self.secondary_sources, source) +function InstallReceiptBuilder:with_source(source) + self.source = source return self end @@ -128,68 +123,21 @@ end function InstallReceiptBuilder:build() assert(self.name, "name is required") - assert(self.schema_version, "schema_version is required") assert(self.start_time, "start_time is required") assert(self.completion_time, "completion_time is required") - assert(self.primary_source, "primary_source is required") + assert(self.source, "source is required") return InstallReceipt.new { name = self.name, - schema_version = self.schema_version, + schema_version = "1.2", metrics = { start_time = self.start_time, completion_time = self.completion_time, }, - primary_source = self.primary_source, - secondary_sources = self.secondary_sources, + source = self.source, links = self.links, } end ----@class InstallReceiptPackageSource ----@field type string ----@field package string - ----@param type InstallReceiptSourceType -local function package_source(type) - ---@param pkg string - ---@return InstallReceiptPackageSource - return function(pkg) - return { type = type, package = pkg } - end -end - -InstallReceiptBuilder.npm = package_source "npm" -InstallReceiptBuilder.pip3 = package_source "pip3" -InstallReceiptBuilder.gem = package_source "gem" -InstallReceiptBuilder.go = package_source "go" -InstallReceiptBuilder.dotnet = package_source "dotnet" -InstallReceiptBuilder.cargo = package_source "cargo" -InstallReceiptBuilder.composer = package_source "composer" -InstallReceiptBuilder.opam = package_source "opam" -InstallReceiptBuilder.luarocks = package_source "luarocks" - -InstallReceiptBuilder.unmanaged = { type = "unmanaged" } - ----@param repo string ----@param release string -function InstallReceiptBuilder.github_release(repo, release) - return { - type = "github_release", - repo = repo, - release = release, - } -end - ----@param dependency string -function InstallReceiptBuilder.system(dependency) - return { type = "system", dependency = dependency } -end - ----@param remote_url string -function InstallReceiptBuilder.git_remote(remote_url) - return { type = "git", remote = remote_url } -end - M.InstallReceiptBuilder = InstallReceiptBuilder M.InstallReceipt = InstallReceipt diff --git a/lua/mason/ui/instance.lua b/lua/mason/ui/instance.lua index 92cfc587e..fb435a4d2 100644 --- a/lua/mason/ui/instance.lua +++ b/lua/mason/ui/instance.lua @@ -307,7 +307,7 @@ local function hydrate_detailed_package_state(pkg) ---@param receipt InstallReceipt function(receipt) mutate_state(function(state) - state.packages.states[pkg.name].linked_executables = receipt.links.bin + state.packages.states[pkg.name].linked_executables = receipt:get_links().bin end) end ) diff --git a/tests/fixtures/receipts/1.0.json b/tests/fixtures/receipts/1.0.json new file mode 100644 index 000000000..e16d68ef8 --- /dev/null +++ b/tests/fixtures/receipts/1.0.json @@ -0,0 +1,23 @@ +{ + "schema_version": "1.0", + "primary_source": { + "type": "npm", + "package": "@angular/language-server" + }, + "links": { + "bin": { + "ngserver": "node_modules/.bin/ngserver" + } + }, + "metrics": { + "start_time": 1694752057715, + "completion_time": 1694752066467 + }, + "secondary_sources": [ + { + "type": "npm", + "package": "typescript" + } + ], + "name": "angular-language-server" +} diff --git a/tests/fixtures/receipts/1.1.json b/tests/fixtures/receipts/1.1.json new file mode 100644 index 000000000..87d6905a0 --- /dev/null +++ b/tests/fixtures/receipts/1.1.json @@ -0,0 +1,27 @@ +{ + "schema_version": "1.1", + "metrics": { + "start_time": 1694752380220, + "completion_time": 1694752386830 + }, + "links": { + "share": {}, + "opt": {}, + "bin": { + "ngserver": "node_modules/.bin/ngserver" + } + }, + "name": "angular-language-server", + "primary_source": { + "type": "registry+v1", + "id": "pkg:npm/%40angular/language-server@16.1.8", + "source": { + "extra_packages": [ + "typescript@5.1.3" + ], + "version": "16.1.8", + "package": "@angular/language-server" + } + }, + "secondary_sources": [] +} diff --git a/tests/fixtures/receipts/1.2.json b/tests/fixtures/receipts/1.2.json new file mode 100644 index 000000000..75a14f09a --- /dev/null +++ b/tests/fixtures/receipts/1.2.json @@ -0,0 +1,19 @@ +{ + "name": "angular-language-server", + "links": { + "bin": { + "ngserver": "node_modules/.bin/ngserver" + }, + "opt": {}, + "share": {} + }, + "metrics": { + "completion_time": 1694752770559, + "start_time": 1694752764840 + }, + "schema_version": "1.2", + "source": { + "type": "registry+v1", + "id": "pkg:npm/%40angular/language-server@16.1.8" + } +} diff --git a/tests/mason-core/installer/installer_spec.lua b/tests/mason-core/installer/installer_spec.lua index 04de82bad..3e2913084 100644 --- a/tests/mason-core/installer/installer_spec.lua +++ b/tests/mason-core/installer/installer_spec.lua @@ -87,12 +87,11 @@ describe("installer", function() local receipt = vim.json.decode(arg) assert.is_true(match.tbl_containing { name = "dummy", - primary_source = match.same { + source = match.same { type = handle.package.spec.schema, id = handle.package.spec.source.id, }, - secondary_sources = match.same {}, - schema_version = "1.1", + schema_version = "1.2", metrics = match.is_table(), links = match.same { bin = { executable = "target" }, diff --git a/tests/mason-core/receipt_spec.lua b/tests/mason-core/receipt_spec.lua new file mode 100644 index 000000000..05ce1439a --- /dev/null +++ b/tests/mason-core/receipt_spec.lua @@ -0,0 +1,86 @@ +local InstallReceipt = require("mason-core.receipt").InstallReceipt +local fs = require "mason-core.fs" + +local function fixture(file) + return vim.json.decode(fs.sync.read_file(("./tests/fixtures/receipts/%s"):format(file))) +end + +describe("receipt ::", function() + it("should parse 1.0 structures", function() + local receipt = InstallReceipt.new(fixture "1.0.json") + + assert.equals("angular-language-server", receipt:get_name()) + assert.equals("1.0", receipt:get_schema_version()) + assert.same({ type = "npm", package = "@angular/language-server" }, receipt:get_source()) + assert.same({ + bin = { + ngserver = "node_modules/.bin/ngserver", + }, + }, receipt:get_links()) + assert.is_true(receipt:is_schema_min "1.0") + end) + + it("should parse 1.1 structures", function() + local receipt = InstallReceipt.new(fixture "1.1.json") + + assert.equals("angular-language-server", receipt:get_name()) + assert.equals("1.1", receipt:get_schema_version()) + assert.same({ + type = "registry+v1", + id = "pkg:npm/%40angular/language-server@16.1.8", + + source = { + extra_packages = { "typescript@5.1.3" }, + version = "16.1.8", + package = "@angular/language-server", + }, + }, receipt:get_source()) + assert.same({ + bin = { + ngserver = "node_modules/.bin/ngserver", + }, + opt = {}, + share = {}, + }, receipt:get_links()) + assert.is_true(receipt:is_schema_min "1.1") + end) + + it("should parse 1.2 structures", function() + local receipt = InstallReceipt.new(fixture "1.2.json") + + assert.equals("angular-language-server", receipt:get_name()) + assert.equals("1.2", receipt:get_schema_version()) + assert.same({ + type = "registry+v1", + id = "pkg:npm/%40angular/language-server@16.1.8", + }, receipt:get_source()) + assert.same({ + bin = { + ngserver = "node_modules/.bin/ngserver", + }, + opt = {}, + share = {}, + }, receipt:get_links()) + assert.is_true(receipt:is_schema_min "1.2") + end) + + describe("schema versions ::", function() + it("should check minimum compatibility", function() + local receipt_1_0 = InstallReceipt.new { schema_version = "1.0" } + local receipt_1_1 = InstallReceipt.new { schema_version = "1.1" } + local receipt_1_2 = InstallReceipt.new { schema_version = "1.2" } + + assert.is_true(receipt_1_0:is_schema_min "1.0") + assert.is_true(receipt_1_1:is_schema_min "1.0") + assert.is_true(receipt_1_2:is_schema_min "1.0") + + assert.is_false(receipt_1_0:is_schema_min "1.1") + assert.is_true(receipt_1_1:is_schema_min "1.1") + assert.is_true(receipt_1_2:is_schema_min "1.1") + + assert.is_false(receipt_1_0:is_schema_min "1.2") + assert.is_false(receipt_1_1:is_schema_min "1.2") + assert.is_true(receipt_1_2:is_schema_min "1.2") + end) + end) +end)