From 49fdc6e080637fd9e5d8a9ae355fa06510cb68ee Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Sun, 8 Oct 2017 02:15:14 -0300 Subject: Reorganize luarocks.core requires and type checking tables * For each `luarocks.core.x` module, make `luarocks.x` module load the core module explicitly as `core`, and expose its relevant methods explicitly as well (instead of using `setmetatable`). * Move all type checking out of the core, adjusting the manifest modules accordingly. * Create separate modules for the rockspec and manifest schemas in the `luarocks.type` namespace. --- src/luarocks/cmd/new_version.lua | 4 +- src/luarocks/cmd/write_rockspec.lua | 4 +- src/luarocks/core/manif.lua | 16 +-- src/luarocks/core/type_check.lua | 231 -------------------------------- src/luarocks/dir.lua | 6 +- src/luarocks/fetch.lua | 6 +- src/luarocks/manif.lua | 37 +++++- src/luarocks/path.lua | 8 +- src/luarocks/persist.lua | 4 +- src/luarocks/type/manifest.lua | 81 +++++++++++ src/luarocks/type/rockspec.lua | 130 ++++++++++++++++++ src/luarocks/type_check.lua | 259 ++++++++++++++++++++---------------- src/luarocks/util.lua | 14 +- src/luarocks/vers.lua | 7 +- 14 files changed, 426 insertions(+), 381 deletions(-) delete mode 100644 src/luarocks/core/type_check.lua create mode 100644 src/luarocks/type/manifest.lua create mode 100644 src/luarocks/type/rockspec.lua (limited to 'src') diff --git a/src/luarocks/cmd/new_version.lua b/src/luarocks/cmd/new_version.lua index b13dbb97..5149f924 100644 --- a/src/luarocks/cmd/new_version.lua +++ b/src/luarocks/cmd/new_version.lua @@ -8,7 +8,7 @@ local download = require("luarocks.download") local fetch = require("luarocks.fetch") local persist = require("luarocks.persist") local fs = require("luarocks.fs") -local type_check = require("luarocks.type_check") +local type_rockspec = require("luarocks.type.rockspec") new_version.help_summary = "Auto-write a rockspec for a new version of a rock." new_version.help_arguments = "[--tag=] [|] [] []" @@ -184,7 +184,7 @@ function new_version.command(flags, input, version, url) local out_filename = out_name.."-"..new_rockver.."-"..new_rev..".rockspec" - persist.save_from_table(out_filename, out_rs, type_check.rockspec_order) + persist.save_from_table(out_filename, out_rs, type_rockspec.order) util.printout("Wrote "..out_filename) diff --git a/src/luarocks/cmd/write_rockspec.lua b/src/luarocks/cmd/write_rockspec.lua index 73010f08..06d50c69 100644 --- a/src/luarocks/cmd/write_rockspec.lua +++ b/src/luarocks/cmd/write_rockspec.lua @@ -7,7 +7,7 @@ local fetch = require("luarocks.fetch") local fs = require("luarocks.fs") local path = require("luarocks.path") local persist = require("luarocks.persist") -local type_check = require("luarocks.type_check") +local type_rockspec = require("luarocks.type.rockspec") local util = require("luarocks.util") local vers = require("luarocks.vers") @@ -364,7 +364,7 @@ function write_rockspec.command(flags, name, version, url_or_dir) rockspec_cleanup(rockspec) - persist.save_from_table(filename, rockspec, type_check.rockspec_order) + persist.save_from_table(filename, rockspec, type_rockspec.order) util.printout() util.printout("Wrote template at "..filename.." -- you should now edit and finish it.") diff --git a/src/luarocks/core/manif.lua b/src/luarocks/core/manif.lua index 549cfb4c..cd8d7a06 100644 --- a/src/luarocks/core/manif.lua +++ b/src/luarocks/core/manif.lua @@ -3,7 +3,6 @@ local manif = {} local persist = require("luarocks.core.persist") -local type_check = require("luarocks.core.type_check") local cfg = require("luarocks.core.cfg") local dir = require("luarocks.core.dir") local require = nil @@ -37,24 +36,15 @@ end -- @param file string: The local filename of the manifest file. -- @param repo_url string: The repository identifier. -- @param lua_version string: Lua version in "5.x" format, defaults to installed version. --- @param quick boolean: If given, skips type checking. -- @return table or (nil, string, string): the manifest or nil, --- error message and error code ("open", "load", "run" or "type"). -function manif.manifest_loader(file, repo_url, lua_version, quick) +-- error message and error code ("open", "load", "run"). +function manif.manifest_loader(file, repo_url, lua_version) local manifest, err, errcode = persist.load_into_table(file) if not manifest then return nil, "Failed loading manifest for "..repo_url..": "..err, errcode end - local globals = err - if not quick then - local ok, err = type_check.type_check_manifest(manifest, globals) - if not ok then - return nil, "Error checking manifest: "..err, "type" - end - end - manif.cache_manifest(repo_url, lua_version, manifest) - return manifest + return manifest, err, errcode end --- Load a local manifest describing a repository. diff --git a/src/luarocks/core/type_check.lua b/src/luarocks/core/type_check.lua deleted file mode 100644 index 8b103a58..00000000 --- a/src/luarocks/core/type_check.lua +++ /dev/null @@ -1,231 +0,0 @@ - -local type_check = {} - -local cfg = require("luarocks.core.cfg") -local vers = require("luarocks.core.vers") -local require = nil --------------------------------------------------------------------------------- - -type_check.string_1 = { _type = "string" } -type_check.number_1 = { _type = "number" } -type_check.mandatory_string_1 = { _type = "string", _mandatory = true } - -local number_1 = type_check.number_1 -local string_1 = type_check.string_1 -local mandatory_string_1 = type_check.mandatory_string_1 - -local manifest_types = { - repository = { - _mandatory = true, - -- packages - _any = { - -- versions - _any = { - -- items - _any = { - arch = mandatory_string_1, - modules = { _any = string_1 }, - commands = { _any = string_1 }, - dependencies = { _any = string_1 }, - -- TODO: to be extended with more metadata. - } - } - } - }, - modules = { - _mandatory = true, - -- modules - _any = { - -- providers - _any = string_1 - } - }, - commands = { - _mandatory = true, - -- modules - _any = { - -- commands - _any = string_1 - } - }, - dependencies = { - -- each module - _any = { - -- each version - _any = { - -- each dependency - _any = { - name = string_1, - constraints = { - _any = { - no_upgrade = { _type = "boolean" }, - op = string_1, - version = { - string = string_1, - _any = number_1, - } - } - } - } - } - } - } -} - -local function check_version(version, typetbl, context) - local typetbl_version = typetbl._version or "1.0" - if vers.compare_versions(typetbl_version, version) then - if context == "" then - return nil, "Invalid rockspec_format version number in rockspec? Please fix rockspec accordingly." - else - return nil, context.." is not supported in rockspec format "..version.." (requires version "..typetbl_version.."), please fix the rockspec_format field accordingly." - end - end - return true -end - ---- Type check an object. --- The object is compared against an archetypical value --- matching the expected type -- the actual values don't matter, --- only their types. Tables are type checked recursively. --- @param version string: The version of the item. --- @param item any: The object being checked. --- @param typetbl any: The type-checking table for the object. --- @param context string: A string indicating the "context" where the --- error occurred (the full table path), for error messages. --- @return boolean or (nil, string): true if type checking --- succeeded, or nil and an error message if it failed. --- @see type_check_table -local function type_check_item(version, item, typetbl, context) - assert(type(version) == "string") - - if typetbl._version and typetbl._version ~= "1.0" then - local ok, err = check_version(version, typetbl, context) - if not ok then - return nil, err - end - end - - local item_type = type(item) or "nil" - local expected_type = typetbl._type or "table" - - if expected_type == "number" then - if not tonumber(item) then - return nil, "Type mismatch on field "..context..": expected a number" - end - elseif expected_type == "string" then - if item_type ~= "string" then - return nil, "Type mismatch on field "..context..": expected a string, got "..item_type - end - if typetbl._pattern then - if not item:match("^"..typetbl._pattern.."$") then - return nil, "Type mismatch on field "..context..": invalid value "..item.." does not match '"..typetbl._pattern.."'" - end - end - elseif expected_type == "table" then - if item_type ~= expected_type then - return nil, "Type mismatch on field "..context..": expected a table" - else - return type_check.type_check_table(version, item, typetbl, context) - end - elseif item_type ~= expected_type then - return nil, "Type mismatch on field "..context..": expected "..expected_type - end - return true -end - -local function mkfield(context, field) - if context == "" then - return tostring(field) - elseif type(field) == "string" then - return context.."."..field - else - return context.."["..tostring(field).."]" - end -end - ---- Type check the contents of a table. --- The table's contents are compared against a reference table, --- which contains the recognized fields, with archetypical values --- matching the expected types -- the actual values of items in the --- reference table don't matter, only their types (ie, for field x --- in tbl that is correctly typed, type(tbl.x) == type(types.x)). --- If the reference table contains a field called MORE, then --- unknown fields in the checked table are accepted. --- If it contains a field called ANY, then its type will be --- used to check any unknown fields. If a field is prefixed --- with MUST_, it is mandatory; its absence from the table is --- a type error. --- Tables are type checked recursively. --- @param version string: The version of tbl. --- @param tbl table: The table to be type checked. --- @param typetbl table: The type-checking table, containing --- values for recognized fields in the checked table. --- @param context string: A string indicating the "context" where the --- error occurred (such as the name of the table the item is a part of), --- to be used by error messages. --- @return boolean or (nil, string): true if type checking --- succeeded, or nil and an error message if it failed. -function type_check.type_check_table(version, tbl, typetbl, context) - assert(type(version) == "string") - assert(type(tbl) == "table") - assert(type(typetbl) == "table") - - local ok, err = check_version(version, typetbl, context) - if not ok then - return nil, err - end - - for k, v in pairs(tbl) do - local t = typetbl[k] or typetbl._any - if t then - local ok, err = type_check_item(version, v, t, mkfield(context, k)) - if not ok then return nil, err end - elseif typetbl._more then - -- Accept unknown field - else - if not cfg.accept_unknown_fields then - return nil, "Unknown field "..k - end - end - end - for k, v in pairs(typetbl) do - if k:sub(1,1) ~= "_" and v._mandatory then - if not tbl[k] then - return nil, "Mandatory field "..mkfield(context, k).." is missing." - end - end - end - return true -end - -function type_check.check_undeclared_globals(globals, typetbl) - local undeclared = {} - for glob, _ in pairs(globals) do - if not (typetbl[glob] or typetbl["MUST_"..glob]) then - table.insert(undeclared, glob) - end - end - if #undeclared == 1 then - return nil, "Unknown variable: "..undeclared[1] - elseif #undeclared > 1 then - return nil, "Unknown variables: "..table.concat(undeclared, ", ") - end - return true -end - ---- Type check a manifest table. --- Verify the correctness of elements from a --- manifest table, reporting on unknown fields and type --- mismatches. --- @return boolean or (nil, string): true if type checking --- succeeded, or nil and an error message if it failed. -function type_check.type_check_manifest(manifest, globals) - assert(type(manifest) == "table") - local ok, err = type_check.check_undeclared_globals(globals, manifest_types) - if not ok then return nil, err end - return type_check.type_check_table("1.0", manifest, manifest_types, "") -end - -return type_check - diff --git a/src/luarocks/dir.lua b/src/luarocks/dir.lua index 71477804..72395e47 100644 --- a/src/luarocks/dir.lua +++ b/src/luarocks/dir.lua @@ -1,7 +1,11 @@ --- Generic utilities for handling pathnames. local dir = {} -setmetatable(dir, { __index = require("luarocks.core.dir") }) + +local core = require("luarocks.core.dir") + +dir.path = core.path +dir.split_url = core.split_url --- Strip the path off a path+filename. -- @param pathname string: A path+name, such as "/a/b/c" diff --git a/src/luarocks/fetch.lua b/src/luarocks/fetch.lua index dc467d19..de4a3dee 100644 --- a/src/luarocks/fetch.lua +++ b/src/luarocks/fetch.lua @@ -4,7 +4,7 @@ local fetch = {} local fs = require("luarocks.fs") local dir = require("luarocks.dir") -local type_check = require("luarocks.type_check") +local type_rockspec = require("luarocks.type.rockspec") local path = require("luarocks.path") local vers = require("luarocks.vers") local persist = require("luarocks.persist") @@ -199,13 +199,13 @@ function fetch.load_local_rockspec(filename, quick) local globals = err if rockspec.rockspec_format then - if vers.compare_versions(rockspec.rockspec_format, type_check.rockspec_format) then + if vers.compare_versions(rockspec.rockspec_format, type_rockspec.rockspec_format) then return nil, "Rockspec format "..rockspec.rockspec_format.." is not supported, please upgrade LuaRocks." end end if not quick then - local ok, err = type_check.type_check_rockspec(rockspec, globals) + local ok, err = type_rockspec.check(rockspec, globals) if not ok then return nil, filename..": "..err end diff --git a/src/luarocks/manif.lua b/src/luarocks/manif.lua index 7f3085db..df9f22ae 100644 --- a/src/luarocks/manif.lua +++ b/src/luarocks/manif.lua @@ -3,8 +3,8 @@ -- They are loaded into manifest tables, which are then used for -- performing searches, matching dependencies, etc. local manif = {} -setmetatable(manif, { __index = require("luarocks.core.manif") }) +local core = require("luarocks.core.manif") local persist = require("luarocks.persist") local fetch = require("luarocks.fetch") local dir = require("luarocks.dir") @@ -12,9 +12,32 @@ local fs = require("luarocks.fs") local cfg = require("luarocks.core.cfg") local path = require("luarocks.path") local util = require("luarocks.util") +local type_manifest = require("luarocks.type.manifest") + +manif.cache_manifest = core.cache_manifest manif.rock_manifest_cache = {} +local function check_manifest(repo_url, manifest, globals) + local ok, err = type_manifest.check(manifest, globals) + if not ok then + core.cache_manifest(repo_url, cfg.lua_version, nil) + return nil, "Error checking manifest: "..err, "type" + end + return manifest +end + +function manif.load_local_manifest(repo_url) + local manifest, err, errcode = core.load_local_manifest(repo_url) + if not manifest then + return nil, err, errcode + end + if err then + return check_manifest(repo_url, manifest, err) + end + return manifest +end + function manif.load_rock_manifest(name, version, root) assert(type(name) == "string") assert(type(version) == "string") @@ -60,7 +83,7 @@ function manif.load_manifest(repo_url, lua_version) assert(type(lua_version) == "string" or not lua_version) lua_version = lua_version or cfg.lua_version - local cached_manifest = manif.get_cached_manifest(repo_url, lua_version) + local cached_manifest = core.get_cached_manifest(repo_url, lua_version) if cached_manifest then return cached_manifest end @@ -94,8 +117,8 @@ function manif.load_manifest(repo_url, lua_version) end if pathname:match(".*%.zip$") then pathname = fs.absolute_name(pathname) - local dir = dir.dir_name(pathname) - fs.change_dir(dir) + local dirname = dir.dir_name(pathname) + fs.change_dir(dirname) local nozip = pathname:match("(.*)%.zip$") fs.delete(nozip) local ok = fs.unzip(pathname) @@ -107,7 +130,11 @@ function manif.load_manifest(repo_url, lua_version) end pathname = nozip end - return manif.manifest_loader(pathname, repo_url, lua_version) + local manifest, err, errcode = core.manifest_loader(pathname, repo_url, lua_version) + if not manifest then + return nil, err, errcode + end + return check_manifest(repo_url, manifest, err) end --- Get type and name of an item (a module or a command) provided by a file. diff --git a/src/luarocks/path.lua b/src/luarocks/path.lua index d740331b..8a56233f 100644 --- a/src/luarocks/path.lua +++ b/src/luarocks/path.lua @@ -3,12 +3,18 @@ -- All paths are configured in this module, making it a single -- point where the layout of the local installation is defined in LuaRocks. local path = {} -setmetatable(path, { __index = require("luarocks.core.path") }) +local core = require("luarocks.core.path") local dir = require("luarocks.dir") local cfg = require("luarocks.core.cfg") local util = require("luarocks.util") +path.rocks_dir = core.rocks_dir +path.versioned_name = core.versioned_name +path.path_to_module = core.path_to_module +path.deploy_lua_dir = core.deploy_lua_dir +path.deploy_lib_dir = core.deploy_lib_dir + --- Infer rockspec filename from a rock filename. -- @param rock_name string: Pathname of a rock file. -- @return string: Filename of the rockspec, without path. diff --git a/src/luarocks/persist.lua b/src/luarocks/persist.lua index 6d5e917b..1460bd15 100644 --- a/src/luarocks/persist.lua +++ b/src/luarocks/persist.lua @@ -4,10 +4,12 @@ -- Implemented separately to avoid interdependencies, -- as it is used in the bootstrapping stage of the cfg module. local persist = {} -setmetatable(persist, { __index = require("luarocks.core.persist") }) +local core = require("luarocks.core.persist") local util = require("luarocks.util") +persist.load_into_table = core.load_into_table + local write_table --- Write a value as Lua code. diff --git a/src/luarocks/type/manifest.lua b/src/luarocks/type/manifest.lua new file mode 100644 index 00000000..f58ff984 --- /dev/null +++ b/src/luarocks/type/manifest.lua @@ -0,0 +1,81 @@ +local type_manifest = {} + +local type_check = require("luarocks.type_check") + +local number_1 = type_check.number_1 +local string_1 = type_check.string_1 +local mandatory_string_1 = type_check.mandatory_string_1 + +local manifest_types = { + repository = { + _mandatory = true, + -- packages + _any = { + -- versions + _any = { + -- items + _any = { + arch = mandatory_string_1, + modules = { _any = string_1 }, + commands = { _any = string_1 }, + dependencies = { _any = string_1 }, + -- TODO: to be extended with more metadata. + } + } + } + }, + modules = { + _mandatory = true, + -- modules + _any = { + -- providers + _any = string_1 + } + }, + commands = { + _mandatory = true, + -- modules + _any = { + -- commands + _any = string_1 + } + }, + dependencies = { + -- each module + _any = { + -- each version + _any = { + -- each dependency + _any = { + name = string_1, + constraints = { + _any = { + no_upgrade = { _type = "boolean" }, + op = string_1, + version = { + string = string_1, + _any = number_1, + } + } + } + } + } + } + } +} + + +--- Type check a manifest table. +-- Verify the correctness of elements from a +-- manifest table, reporting on unknown fields and type +-- mismatches. +-- @return boolean or (nil, string): true if type checking +-- succeeded, or nil and an error message if it failed. +function type_manifest.check(manifest, globals) + assert(type(manifest) == "table") + local ok, err = type_check.check_undeclared_globals(globals, manifest_types) + if not ok then return nil, err end + return type_check.type_check_table("1.0", manifest, manifest_types, "") +end + +return type_manifest diff --git a/src/luarocks/type/rockspec.lua b/src/luarocks/type/rockspec.lua new file mode 100644 index 00000000..5ff48177 --- /dev/null +++ b/src/luarocks/type/rockspec.lua @@ -0,0 +1,130 @@ +local type_rockspec = {} + +local type_check = require("luarocks.type_check") + +type_rockspec.rockspec_format = "3.0" + +local string_1 = type_check.string_1 +local mandatory_string_1 = type_check.mandatory_string_1 + +local string_3 = { _type = "string", _version = "3.0" } +local list_of_strings_3 = { _any = string_3, _version = "3.0" } + +-- Syntax for type-checking tables: +-- +-- A type-checking table describes typing data for a value. +-- Any key starting with an underscore has a special meaning: +-- _type (string) is the Lua type of the value. Default is "table". +-- _version (string) is the minimum rockspec_version that supports this value. Default is "1.0". +-- _mandatory (boolean) indicates if the value is a mandatory key in its container table. Default is false. +-- For "string" types only: +-- _pattern (string) is the string-matching pattern, valid for string types only. Default is ".*". +-- For "table" types only: +-- _any (table) is the type-checking table for unspecified keys, recursively checked. +-- _more (boolean) indicates that the table accepts unspecified keys and does not type-check them. +-- Any other string keys that don't start with an underscore represent known keys and are type-checking tables, recursively checked. + +local rockspec_types = { + rockspec_format = string_1, + package = mandatory_string_1, + version = { _type = "string", _pattern = "[%w.]+-[%d]+", _mandatory = true }, + description = { + summary = string_1, + detailed = string_1, + homepage = string_1, + license = string_1, + maintainer = string_1, + labels = list_of_strings_3, + issues_url = string_3, + }, + dependencies = { + platforms = {}, -- recursively defined below + _any = string_1, + }, + supported_platforms = { + _any = string_1, + }, + external_dependencies = { + platforms = {}, -- recursively defined below + _any = { + program = string_1, + header = string_1, + library = string_1, + } + }, + source = { + _mandatory = true, + platforms = {}, -- recursively defined below + url = mandatory_string_1, + md5 = string_1, + file = string_1, + dir = string_1, + tag = string_1, + branch = string_1, + module = string_1, + cvs_tag = string_1, + cvs_module = string_1, + }, + build = { + platforms = {}, -- recursively defined below + type = string_1, + install = { + lua = { + _more = true + }, + lib = { + _more = true + }, + conf = { + _more = true + }, + bin = { + _more = true + } + }, + copy_directories = { + _any = string_1, + }, + _more = true, + _mandatory = true + }, + hooks = { + platforms = {}, -- recursively defined below + post_install = string_1, + }, + deploy = { + _version = "1.1", + wrap_bin_scripts = { _type = "boolean", _version = "1.1" }, + } +} + +type_rockspec.order = {"rockspec_format", "package", "version", + { "source", { "url", "tag", "branch", "md5" } }, + { "description", {"summary", "detailed", "homepage", "license" } }, + "supported_platforms", "dependencies", "external_dependencies", + { "build", {"type", "modules", "copy_directories", "platforms"} }, + "hooks"} + +rockspec_types.build.platforms._any = rockspec_types.build +rockspec_types.dependencies.platforms._any = rockspec_types.dependencies +rockspec_types.external_dependencies.platforms._any = rockspec_types.external_dependencies +rockspec_types.source.platforms._any = rockspec_types.source +rockspec_types.hooks.platforms._any = rockspec_types.hooks + +--- Type check a rockspec table. +-- Verify the correctness of elements from a +-- rockspec table, reporting on unknown fields and type +-- mismatches. +-- @return boolean or (nil, string): true if type checking +-- succeeded, or nil and an error message if it failed. +function type_rockspec.check(rockspec, globals) + assert(type(rockspec) == "table") + if not rockspec.rockspec_format then + rockspec.rockspec_format = "1.0" + end + local ok, err = type_check.check_undeclared_globals(globals, rockspec_types) + if not ok then return nil, err end + return type_check.type_check_table(rockspec.rockspec_format, rockspec, rockspec_types, "") +end + +return type_rockspec diff --git a/src/luarocks/type_check.lua b/src/luarocks/type_check.lua index 4b7e68d7..250c6258 100644 --- a/src/luarocks/type_check.lua +++ b/src/luarocks/type_check.lua @@ -1,132 +1,155 @@ ---- Type-checking functions. --- Functions and definitions for doing a basic lint check on files --- loaded by LuaRocks. -local type_check = {} -setmetatable(type_check, { __index = require("luarocks.core.type_check") }) -type_check.rockspec_format = "3.0" +local type_check = {} -local string_1 = type_check.string_1 -local mandatory_string_1 = type_check.mandatory_string_1 +local cfg = require("luarocks.core.cfg") +local vers = require("luarocks.core.vers") +local require = nil +-------------------------------------------------------------------------------- -local string_3 = { _type = "string", _version = "3.0" } -local list_of_strings_3 = { _any = string_3, _version = "3.0" } +type_check.string_1 = { _type = "string" } +type_check.number_1 = { _type = "number" } +type_check.mandatory_string_1 = { _type = "string", _mandatory = true } --- Syntax for type-checking tables: --- --- A type-checking table describes typing data for a value. --- Any key starting with an underscore has a special meaning: --- _type (string) is the Lua type of the value. Default is "table". --- _version (string) is the minimum rockspec_version that supports this value. Default is "1.0". --- _mandatory (boolean) indicates if the value is a mandatory key in its container table. Default is false. --- For "string" types only: --- _pattern (string) is the string-matching pattern, valid for string types only. Default is ".*". --- For "table" types only: --- _any (table) is the type-checking table for unspecified keys, recursively checked. --- _more (boolean) indicates that the table accepts unspecified keys and does not type-check them. --- Any other string keys that don't start with an underscore represent known keys and are type-checking tables, recursively checked. +local function check_version(version, typetbl, context) + local typetbl_version = typetbl._version or "1.0" + if vers.compare_versions(typetbl_version, version) then + if context == "" then + return nil, "Invalid rockspec_format version number in rockspec? Please fix rockspec accordingly." + else + return nil, context.." is not supported in rockspec format "..version.." (requires version "..typetbl_version.."), please fix the rockspec_format field accordingly." + end + end + return true +end -local rockspec_types = { - rockspec_format = string_1, - package = mandatory_string_1, - version = { _type = "string", _pattern = "[%w.]+-[%d]+", _mandatory = true }, - description = { - summary = string_1, - detailed = string_1, - homepage = string_1, - license = string_1, - maintainer = string_1, - labels = list_of_strings_3, - issues_url = string_3, - }, - dependencies = { - platforms = {}, -- recursively defined below - _any = string_1, - }, - supported_platforms = { - _any = string_1, - }, - external_dependencies = { - platforms = {}, -- recursively defined below - _any = { - program = string_1, - header = string_1, - library = string_1, - } - }, - source = { - _mandatory = true, - platforms = {}, -- recursively defined below - url = mandatory_string_1, - md5 = string_1, - file = string_1, - dir = string_1, - tag = string_1, - branch = string_1, - module = string_1, - cvs_tag = string_1, - cvs_module = string_1, - }, - build = { - platforms = {}, -- recursively defined below - type = string_1, - install = { - lua = { - _more = true - }, - lib = { - _more = true - }, - conf = { - _more = true - }, - bin = { - _more = true - } - }, - copy_directories = { - _any = string_1, - }, - _more = true, - _mandatory = true - }, - hooks = { - platforms = {}, -- recursively defined below - post_install = string_1, - }, - deploy = { - _version = "1.1", - wrap_bin_scripts = { _type = "boolean", _version = "1.1" }, - } -} +--- Type check an object. +-- The object is compared against an archetypical value +-- matching the expected type -- the actual values don't matter, +-- only their types. Tables are type checked recursively. +-- @param version string: The version of the item. +-- @param item any: The object being checked. +-- @param typetbl any: The type-checking table for the object. +-- @param context string: A string indicating the "context" where the +-- error occurred (the full table path), for error messages. +-- @return boolean or (nil, string): true if type checking +-- succeeded, or nil and an error message if it failed. +-- @see type_check_table +local function type_check_item(version, item, typetbl, context) + assert(type(version) == "string") -type_check.rockspec_order = {"rockspec_format", "package", "version", - { "source", { "url", "tag", "branch", "md5" } }, - { "description", {"summary", "detailed", "homepage", "license" } }, - "supported_platforms", "dependencies", "external_dependencies", - { "build", {"type", "modules", "copy_directories", "platforms"} }, - "hooks"} + if typetbl._version and typetbl._version ~= "1.0" then + local ok, err = check_version(version, typetbl, context) + if not ok then + return nil, err + end + end + + local item_type = type(item) or "nil" + local expected_type = typetbl._type or "table" + + if expected_type == "number" then + if not tonumber(item) then + return nil, "Type mismatch on field "..context..": expected a number" + end + elseif expected_type == "string" then + if item_type ~= "string" then + return nil, "Type mismatch on field "..context..": expected a string, got "..item_type + end + if typetbl._pattern then + if not item:match("^"..typetbl._pattern.."$") then + return nil, "Type mismatch on field "..context..": invalid value "..item.." does not match '"..typetbl._pattern.."'" + end + end + elseif expected_type == "table" then + if item_type ~= expected_type then + return nil, "Type mismatch on field "..context..": expected a table" + else + return type_check.type_check_table(version, item, typetbl, context) + end + elseif item_type ~= expected_type then + return nil, "Type mismatch on field "..context..": expected "..expected_type + end + return true +end -rockspec_types.build.platforms._any = rockspec_types.build -rockspec_types.dependencies.platforms._any = rockspec_types.dependencies -rockspec_types.external_dependencies.platforms._any = rockspec_types.external_dependencies -rockspec_types.source.platforms._any = rockspec_types.source -rockspec_types.hooks.platforms._any = rockspec_types.hooks +local function mkfield(context, field) + if context == "" then + return tostring(field) + elseif type(field) == "string" then + return context.."."..field + else + return context.."["..tostring(field).."]" + end +end ---- Type check a rockspec table. --- Verify the correctness of elements from a --- rockspec table, reporting on unknown fields and type --- mismatches. +--- Type check the contents of a table. +-- The table's contents are compared against a reference table, +-- which contains the recognized fields, with archetypical values +-- matching the expected types -- the actual values of items in the +-- reference table don't matter, only their types (ie, for field x +-- in tbl that is correctly typed, type(tbl.x) == type(types.x)). +-- If the reference table contains a field called MORE, then +-- unknown fields in the checked table are accepted. +-- If it contains a field called ANY, then its type will be +-- used to check any unknown fields. If a field is prefixed +-- with MUST_, it is mandatory; its absence from the table is +-- a type error. +-- Tables are type checked recursively. +-- @param version string: The version of tbl. +-- @param tbl table: The table to be type checked. +-- @param typetbl table: The type-checking table, containing +-- values for recognized fields in the checked table. +-- @param context string: A string indicating the "context" where the +-- error occurred (such as the name of the table the item is a part of), +-- to be used by error messages. -- @return boolean or (nil, string): true if type checking -- succeeded, or nil and an error message if it failed. -function type_check.type_check_rockspec(rockspec, globals) - assert(type(rockspec) == "table") - if not rockspec.rockspec_format then - rockspec.rockspec_format = "1.0" +function type_check.type_check_table(version, tbl, typetbl, context) + assert(type(version) == "string") + assert(type(tbl) == "table") + assert(type(typetbl) == "table") + + local ok, err = check_version(version, typetbl, context) + if not ok then + return nil, err + end + + for k, v in pairs(tbl) do + local t = typetbl[k] or typetbl._any + if t then + local ok, err = type_check_item(version, v, t, mkfield(context, k)) + if not ok then return nil, err end + elseif typetbl._more then + -- Accept unknown field + else + if not cfg.accept_unknown_fields then + return nil, "Unknown field "..k + end + end + end + for k, v in pairs(typetbl) do + if k:sub(1,1) ~= "_" and v._mandatory then + if not tbl[k] then + return nil, "Mandatory field "..mkfield(context, k).." is missing." + end + end + end + return true +end + +function type_check.check_undeclared_globals(globals, typetbl) + local undeclared = {} + for glob, _ in pairs(globals) do + if not (typetbl[glob] or typetbl["MUST_"..glob]) then + table.insert(undeclared, glob) + end + end + if #undeclared == 1 then + return nil, "Unknown variable: "..undeclared[1] + elseif #undeclared > 1 then + return nil, "Unknown variables: "..table.concat(undeclared, ", ") end - local ok, err = type_check.check_undeclared_globals(globals, rockspec_types) - if not ok then return nil, err end - return type_check.type_check_table(rockspec.rockspec_format, rockspec, rockspec_types, "") + return true end return type_check diff --git a/src/luarocks/util.lua b/src/luarocks/util.lua index 59173449..0af23df6 100644 --- a/src/luarocks/util.lua +++ b/src/luarocks/util.lua @@ -5,7 +5,15 @@ -- as this is used in the bootstrapping stage of luarocks.core.cfg. local util = {} -setmetatable(util, { __index = require("luarocks.core.util") }) + +local core = require("luarocks.core.util") + +util.popen_read = core.popen_read +util.cleanup_path = core.cleanup_path +util.split_string = core.split_string +util.keys = core.keys +util.printerr = core.printerr +util.sortedpairs = core.sortedpairs local unpack = unpack or table.unpack @@ -223,7 +231,7 @@ function util.platform_overrides(tbl) for _, platform in ipairs(cfg.platforms) do local platform_tbl = tbl.platforms[platform] if platform_tbl then - util.deep_merge(tbl, platform_tbl) + core.deep_merge(tbl, platform_tbl) end end end @@ -243,7 +251,7 @@ local var_format_pattern = "%$%((%a[%a%d_]+)%)" -- needed variables. -- @param msg string: the warning message to display. function util.warn_if_not_used(var_defs, needed_set, msg) - needed_set = util.make_shallow_copy(needed_set) + needed_set = core.make_shallow_copy(needed_set) for _, val in pairs(var_defs) do for used in val:gmatch(var_format_pattern) do needed_set[used] = nil diff --git a/src/luarocks/vers.lua b/src/luarocks/vers.lua index d37690a8..c1dfbd39 100644 --- a/src/luarocks/vers.lua +++ b/src/luarocks/vers.lua @@ -10,7 +10,12 @@ -- "common sense" heuristics. The precise specification of the -- comparison criteria is the source code of this module. local vers = {} -setmetatable(vers, { __index = require("luarocks.core.vers") }) + +local core = require("luarocks.core.vers") + +vers.parse_version = core.parse_version +vers.compare_versions = core.compare_versions +vers.match_constraints = core.match_constraints --- Check if rockspec format version satisfies version requirement. -- @param rockspec table: The rockspec table. -- cgit v1.2.3-55-g6feb