diff options
| author | Hisham Muhammad <hisham@gobolinux.org> | 2014-09-09 23:05:59 -0300 |
|---|---|---|
| committer | Hisham Muhammad <hisham@gobolinux.org> | 2014-09-09 23:05:59 -0300 |
| commit | f0d66aeacd60e6df926a55ecb9a08e6f802f9242 (patch) | |
| tree | 9f8a83fe0756a4d7103e1fe9513f04887d05cfd4 | |
| parent | 0587afbb5fe8ceb2f2eea16f486bd6183bf02f29 (diff) | |
| download | luarocks-f0d66aeacd60e6df926a55ecb9a08e6f802f9242.tar.gz luarocks-f0d66aeacd60e6df926a55ecb9a08e6f802f9242.tar.bz2 luarocks-f0d66aeacd60e6df926a55ecb9a08e6f802f9242.zip | |
Support per-field version checking.
This will allow us to add fields and bump rockspec version numbers
in a well-behaved manner.
| -rw-r--r-- | src/luarocks/type_check.lua | 274 |
1 files changed, 158 insertions, 116 deletions
diff --git a/src/luarocks/type_check.lua b/src/luarocks/type_check.lua index a78c4848..4adb7cf4 100644 --- a/src/luarocks/type_check.lua +++ b/src/luarocks/type_check.lua | |||
| @@ -6,72 +6,95 @@ local type_check = {} | |||
| 6 | package.loaded["luarocks.type_check"] = type_check | 6 | package.loaded["luarocks.type_check"] = type_check |
| 7 | 7 | ||
| 8 | local cfg = require("luarocks.cfg") | 8 | local cfg = require("luarocks.cfg") |
| 9 | local deps = require("luarocks.deps") | ||
| 9 | 10 | ||
| 10 | type_check.rockspec_format = "1.0" | 11 | type_check.rockspec_format = "1.1" |
| 12 | |||
| 13 | local string_1 = { _type = "string" } | ||
| 14 | local mandatory_string_1 = { _type = "string", _mandatory = true } | ||
| 15 | |||
| 16 | -- Syntax for type-checking tables: | ||
| 17 | -- | ||
| 18 | -- A type-checking table describes typing data for a value. | ||
| 19 | -- Any key starting with an underscore has a special meaning: | ||
| 20 | -- _type (string) is the Lua type of the value. Default is "table". | ||
| 21 | -- _version (string) is the minimum rockspec_version that supports this value. Default is "1.0". | ||
| 22 | -- _mandatory (boolean) indicates if the value is a mandatory key in its container table. Default is false. | ||
| 23 | -- For "string" types only: | ||
| 24 | -- _pattern (string) is the string-matching pattern, valid for string types only. Default is ".*". | ||
| 25 | -- For "table" types only: | ||
| 26 | -- _any (table) is the type-checking table for unspecified keys, recursively checked. | ||
| 27 | -- _more (boolean) indicates that the table accepts unspecified keys and does not type-check them. | ||
| 28 | -- Any other string keys that don't start with an underscore represent known keys and are type-checking tables, recursively checked. | ||
| 11 | 29 | ||
| 12 | local rockspec_types = { | 30 | local rockspec_types = { |
| 13 | rockspec_format = "string", | 31 | rockspec_format = string_1, |
| 14 | MUST_package = "string", | 32 | package = mandatory_string_1, |
| 15 | MUST_version = "[%w.]+-[%d]+", | 33 | version = { _type = "string", _pattern = "[%w.]+-[%d]+", _mandatory = true }, |
| 16 | description = { | 34 | description = { |
| 17 | summary = "string", | 35 | summary = string_1, |
| 18 | detailed = "string", | 36 | detailed = string_1, |
| 19 | homepage = "string", | 37 | homepage = string_1, |
| 20 | license = "string", | 38 | license = string_1, |
| 21 | maintainer = "string" | 39 | maintainer = string_1, |
| 22 | }, | 40 | }, |
| 23 | dependencies = { | 41 | dependencies = { |
| 24 | platforms = {}, | 42 | platforms = {}, -- recursively defined below |
| 25 | ANY = "string" | 43 | _any = string_1, |
| 26 | }, | 44 | }, |
| 27 | supported_platforms = { | 45 | supported_platforms = { |
| 28 | ANY = "string" | 46 | _any = string_1, |
| 29 | }, | 47 | }, |
| 30 | external_dependencies = { | 48 | external_dependencies = { |
| 31 | platforms = {}, | 49 | platforms = {}, -- recursively defined below |
| 32 | ANY = { | 50 | _any = { |
| 33 | program = "string", | 51 | program = string_1, |
| 34 | header = "string", | 52 | header = string_1, |
| 35 | library = "string" | 53 | library = string_1, |
| 36 | } | 54 | } |
| 37 | }, | 55 | }, |
| 38 | MUST_source = { | 56 | source = { |
| 39 | platforms = {}, | 57 | _mandatory = true, |
| 40 | MUST_url = "string", | 58 | platforms = {}, -- recursively defined below |
| 41 | md5 = "string", | 59 | url = mandatory_string_1, |
| 42 | file = "string", | 60 | md5 = string_1, |
| 43 | dir = "string", | 61 | file = string_1, |
| 44 | tag = "string", | 62 | dir = string_1, |
| 45 | branch = "string", | 63 | tag = string_1, |
| 46 | module = "string", | 64 | branch = string_1, |
| 47 | cvs_tag = "string", | 65 | module = string_1, |
| 48 | cvs_module = "string" | 66 | cvs_tag = string_1, |
| 67 | cvs_module = string_1, | ||
| 49 | }, | 68 | }, |
| 50 | build = { | 69 | build = { |
| 51 | platforms = {}, | 70 | platforms = {}, -- recursively defined below |
| 52 | type = "string", | 71 | type = string_1, |
| 53 | install = { | 72 | install = { |
| 54 | lua = { | 73 | lua = { |
| 55 | MORE = true | 74 | _more = true |
| 56 | }, | 75 | }, |
| 57 | lib = { | 76 | lib = { |
| 58 | MORE = true | 77 | _more = true |
| 59 | }, | 78 | }, |
| 60 | conf = { | 79 | conf = { |
| 61 | MORE = true | 80 | _more = true |
| 62 | }, | 81 | }, |
| 63 | bin = { | 82 | bin = { |
| 64 | MORE = true | 83 | _more = true |
| 65 | } | 84 | } |
| 66 | }, | 85 | }, |
| 67 | copy_directories = { | 86 | copy_directories = { |
| 68 | ANY = "string" | 87 | _any = string_1, |
| 69 | }, | 88 | }, |
| 70 | MORE = true | 89 | _more = true |
| 71 | }, | 90 | }, |
| 72 | hooks = { | 91 | hooks = { |
| 73 | platforms = {}, | 92 | platforms = {}, -- recursively defined below |
| 74 | post_install = "string" | 93 | post_install = string_1, |
| 94 | }, | ||
| 95 | deploy = { | ||
| 96 | _version = "1.1", | ||
| 97 | wrap_bin_scripts = { _type = "boolean", _version = "1.1" }, | ||
| 75 | } | 98 | } |
| 76 | } | 99 | } |
| 77 | 100 | ||
| @@ -82,69 +105,61 @@ type_check.rockspec_order = {"rockspec_format", "package", "version", | |||
| 82 | { "build", {"type", "modules", "copy_directories", "platforms"} }, | 105 | { "build", {"type", "modules", "copy_directories", "platforms"} }, |
| 83 | "hooks"} | 106 | "hooks"} |
| 84 | 107 | ||
| 85 | function type_check.load_extensions() | 108 | rockspec_types.build.platforms._any = rockspec_types.build |
| 86 | type_check.rockspec_format = "1.1" | 109 | rockspec_types.dependencies.platforms._any = rockspec_types.dependencies |
| 87 | rockspec_types.deploy = { | 110 | rockspec_types.external_dependencies.platforms._any = rockspec_types.external_dependencies |
| 88 | wrap_bin_scripts = true, | 111 | rockspec_types.source.platforms._any = rockspec_types.source |
| 89 | } | 112 | rockspec_types.hooks.platforms._any = rockspec_types.hooks |
| 90 | end | ||
| 91 | |||
| 92 | if cfg.use_extensions then | ||
| 93 | type_check.load_extensions() | ||
| 94 | end | ||
| 95 | |||
| 96 | rockspec_types.build.platforms.ANY = rockspec_types.build | ||
| 97 | rockspec_types.dependencies.platforms.ANY = rockspec_types.dependencies | ||
| 98 | rockspec_types.external_dependencies.platforms.ANY = rockspec_types.external_dependencies | ||
| 99 | rockspec_types.MUST_source.platforms.ANY = rockspec_types.MUST_source | ||
| 100 | rockspec_types.hooks.platforms.ANY = rockspec_types.hooks | ||
| 101 | 113 | ||
| 102 | local manifest_types = { | 114 | local manifest_types = { |
| 103 | MUST_repository = { | 115 | repository = { |
| 116 | _mandatory = true, | ||
| 104 | -- packages | 117 | -- packages |
| 105 | ANY = { | 118 | _any = { |
| 106 | -- versions | 119 | -- versions |
| 107 | ANY = { | 120 | _any = { |
| 108 | -- items | 121 | -- items |
| 109 | ANY = { | 122 | _any = { |
| 110 | MUST_arch = "string", | 123 | arch = mandatory_string_1, |
| 111 | modules = { ANY = "string" }, | 124 | modules = { _any = string_1 }, |
| 112 | commands = { ANY = "string" }, | 125 | commands = { _any = string_1 }, |
| 113 | dependencies = { ANY = "string" }, | 126 | dependencies = { _any = string_1 }, |
| 114 | -- TODO: to be extended with more metadata. | 127 | -- TODO: to be extended with more metadata. |
| 115 | } | 128 | } |
| 116 | } | 129 | } |
| 117 | } | 130 | } |
| 118 | }, | 131 | }, |
| 119 | MUST_modules = { | 132 | modules = { |
| 133 | _mandatory = true, | ||
| 120 | -- modules | 134 | -- modules |
| 121 | ANY = { | 135 | _any = { |
| 122 | -- providers | 136 | -- providers |
| 123 | ANY = "string" | 137 | _any = string_1 |
| 124 | } | 138 | } |
| 125 | }, | 139 | }, |
| 126 | MUST_commands = { | 140 | commands = { |
| 141 | _mandatory = true, | ||
| 127 | -- modules | 142 | -- modules |
| 128 | ANY = { | 143 | _any = { |
| 129 | -- commands | 144 | -- commands |
| 130 | ANY = "string" | 145 | _any = string_1 |
| 131 | } | 146 | } |
| 132 | }, | 147 | }, |
| 133 | dependencies = { | 148 | dependencies = { |
| 134 | -- each module | 149 | -- each module |
| 135 | ANY = { | 150 | _any = { |
| 136 | -- each version | 151 | -- each version |
| 137 | ANY = { | 152 | _any = { |
| 138 | -- each dependency | 153 | -- each dependency |
| 139 | ANY = { | 154 | _any = { |
| 140 | name = "string", | 155 | name = string_1, |
| 141 | constraints = { | 156 | constraints = { |
| 142 | ANY = { | 157 | _any = { |
| 143 | no_upgrade = "boolean", | 158 | no_upgrade = { _type = "boolean" }, |
| 144 | op = "string", | 159 | op = string_1, |
| 145 | version = { | 160 | version = { |
| 146 | string = "string", | 161 | string = string_1, |
| 147 | ANY = 0, | 162 | _any = 0, |
| 148 | } | 163 | } |
| 149 | } | 164 | } |
| 150 | } | 165 | } |
| @@ -154,54 +169,75 @@ local manifest_types = { | |||
| 154 | } | 169 | } |
| 155 | } | 170 | } |
| 156 | 171 | ||
| 172 | local function check_version(version, typetbl, context) | ||
| 173 | local typetbl_version = typetbl._version or "1.0" | ||
| 174 | if deps.compare_versions(typetbl_version, version) then | ||
| 175 | if context == "" then | ||
| 176 | return nil, "Invalid rockspec_format version number in rockspec? Please fix rockspec accordingly." | ||
| 177 | else | ||
| 178 | return nil, context.." is not supported in rockspec format "..version.." (requires version "..typetbl_version.."), please fix the rockspec_format field accordingly." | ||
| 179 | end | ||
| 180 | end | ||
| 181 | return true | ||
| 182 | end | ||
| 183 | |||
| 157 | local type_check_table | 184 | local type_check_table |
| 158 | 185 | ||
| 159 | --- Type check an object. | 186 | --- Type check an object. |
| 160 | -- The object is compared against an archetypical value | 187 | -- The object is compared against an archetypical value |
| 161 | -- matching the expected type -- the actual values don't matter, | 188 | -- matching the expected type -- the actual values don't matter, |
| 162 | -- only their types. Tables are type checked recursively. | 189 | -- only their types. Tables are type checked recursively. |
| 163 | -- @param name any: The object name (for error messages). | 190 | -- @param version string: The version of the item. |
| 164 | -- @param item any: The object being checked. | 191 | -- @param item any: The object being checked. |
| 165 | -- @param expected any: The reference object. In case of a table, | 192 | -- @param typetbl any: The type-checking table for the object. |
| 166 | -- its is structured as a type reference table. | ||
| 167 | -- @param context string: A string indicating the "context" where the | 193 | -- @param context string: A string indicating the "context" where the |
| 168 | -- error occurred (such as the name of the table the item is a part of), | 194 | -- error occurred (the full table path), for error messages. |
| 169 | -- to be used by error messages. | ||
| 170 | -- @return boolean or (nil, string): true if type checking | 195 | -- @return boolean or (nil, string): true if type checking |
| 171 | -- succeeded, or nil and an error message if it failed. | 196 | -- succeeded, or nil and an error message if it failed. |
| 172 | -- @see type_check_table | 197 | -- @see type_check_table |
| 173 | local function type_check_item(name, item, expected, context) | 198 | local function type_check_item(version, item, typetbl, context) |
| 174 | name = tostring(name) | 199 | assert(type(version) == "string") |
| 175 | 200 | ||
| 176 | local item_type = type(item) | 201 | local ok, err = check_version(version, typetbl, context) |
| 177 | local expected_type = type(expected) | 202 | if not ok then |
| 203 | return nil, err | ||
| 204 | end | ||
| 205 | |||
| 206 | local item_type = type(item) or "nil" | ||
| 207 | local expected_type = typetbl._type or "table" | ||
| 208 | |||
| 178 | if expected_type == "number" then | 209 | if expected_type == "number" then |
| 179 | if not tonumber(item) then | 210 | if not tonumber(item) then |
| 180 | return nil, "Type mismatch on field "..context..name..": expected a number" | 211 | return nil, "Type mismatch on field "..context..": expected a number" |
| 181 | end | 212 | end |
| 182 | elseif expected_type == "string" then | 213 | elseif expected_type == "string" then |
| 183 | if type(item) ~= "string" then | 214 | if item_type ~= "string" then |
| 184 | return nil, "Type mismatch on field "..context..name..": expected a string" | 215 | return nil, "Type mismatch on field "..context..": expected a string, got "..item_type |
| 185 | end | 216 | end |
| 186 | if expected ~= "string" then | 217 | if typetbl._pattern then |
| 187 | if item_type ~= "string" then | 218 | if not item:match("^"..typetbl._pattern.."$") then |
| 188 | return nil, "Type mismatch on field "..context..name..": expected a string, got a "..type(item) | 219 | return nil, "Type mismatch on field "..context..": invalid value "..item.." does not match '"..expected.."'" |
| 189 | elseif not item:match("^"..expected.."$") then | ||
| 190 | return nil, "Type mismatch on field "..context..name..": invalid value "..item.." does not match '"..expected.."'" | ||
| 191 | end | 220 | end |
| 192 | end | 221 | end |
| 193 | elseif expected_type == "table" then | 222 | elseif expected_type == "table" then |
| 194 | if item_type ~= expected_type then | 223 | if item_type ~= expected_type then |
| 195 | return nil, "Type mismatch on field "..context..name..": expected a table" | 224 | return nil, "Type mismatch on field "..context..": expected a table" |
| 196 | else | 225 | else |
| 197 | return type_check_table(item, expected, context..name..".") | 226 | return type_check_table(version, item, typetbl, context) |
| 198 | end | 227 | end |
| 199 | elseif item_type ~= expected_type then | 228 | elseif item_type ~= expected_type then |
| 200 | return nil, "Type mismatch on field "..context..name..": expected a "..expected_type | 229 | return nil, "Type mismatch on field "..context..": expected "..expected_type |
| 201 | end | 230 | end |
| 202 | return true | 231 | return true |
| 203 | end | 232 | end |
| 204 | 233 | ||
| 234 | local function mkfield(context, field) | ||
| 235 | if context == "" then | ||
| 236 | return field | ||
| 237 | end | ||
| 238 | return context.."."..field | ||
| 239 | end | ||
| 240 | |||
| 205 | --- Type check the contents of a table. | 241 | --- Type check the contents of a table. |
| 206 | -- The table's contents are compared against a reference table, | 242 | -- The table's contents are compared against a reference table, |
| 207 | -- which contains the recognized fields, with archetypical values | 243 | -- which contains the recognized fields, with archetypical values |
| @@ -215,23 +251,31 @@ end | |||
| 215 | -- with MUST_, it is mandatory; its absence from the table is | 251 | -- with MUST_, it is mandatory; its absence from the table is |
| 216 | -- a type error. | 252 | -- a type error. |
| 217 | -- Tables are type checked recursively. | 253 | -- Tables are type checked recursively. |
| 254 | -- @param version string: The version of tbl. | ||
| 218 | -- @param tbl table: The table to be type checked. | 255 | -- @param tbl table: The table to be type checked. |
| 219 | -- @param types table: The reference table, containing | 256 | -- @param typetbl table: The type-checking table, containing |
| 220 | -- values for recognized fields in the checked table. | 257 | -- values for recognized fields in the checked table. |
| 221 | -- @param context string: A string indicating the "context" where the | 258 | -- @param context string: A string indicating the "context" where the |
| 222 | -- error occurred (such as the name of the table the item is a part of), | 259 | -- error occurred (such as the name of the table the item is a part of), |
| 223 | -- to be used by error messages. | 260 | -- to be used by error messages. |
| 224 | -- @return boolean or (nil, string): true if type checking | 261 | -- @return boolean or (nil, string): true if type checking |
| 225 | -- succeeded, or nil and an error message if it failed. | 262 | -- succeeded, or nil and an error message if it failed. |
| 226 | type_check_table = function(tbl, types, context) | 263 | type_check_table = function(version, tbl, typetbl, context) |
| 264 | assert(type(version) == "string") | ||
| 227 | assert(type(tbl) == "table") | 265 | assert(type(tbl) == "table") |
| 228 | assert(type(types) == "table") | 266 | assert(type(typetbl) == "table") |
| 267 | |||
| 268 | local ok, err = check_version(version, typetbl, context) | ||
| 269 | if not ok then | ||
| 270 | return nil, err | ||
| 271 | end | ||
| 272 | |||
| 229 | for k, v in pairs(tbl) do | 273 | for k, v in pairs(tbl) do |
| 230 | local t = types[k] or (type(k) == "string" and types["MUST_"..k]) or types.ANY | 274 | local t = typetbl[k] or typetbl._any |
| 231 | if t then | 275 | if t then |
| 232 | local ok, err = type_check_item(k, v, t, context) | 276 | local ok, err = type_check_item(version, v, t, mkfield(context, k)) |
| 233 | if not ok then return nil, err end | 277 | if not ok then return nil, err end |
| 234 | elseif types.MORE then | 278 | elseif typetbl._more then |
| 235 | -- Accept unknown field | 279 | -- Accept unknown field |
| 236 | else | 280 | else |
| 237 | if not cfg.accept_unknown_fields then | 281 | if not cfg.accept_unknown_fields then |
| @@ -239,21 +283,20 @@ type_check_table = function(tbl, types, context) | |||
| 239 | end | 283 | end |
| 240 | end | 284 | end |
| 241 | end | 285 | end |
| 242 | for k, v in pairs(types) do | 286 | for k, v in pairs(typetbl) do |
| 243 | local mandatory_key = k:match("^MUST_(.+)") | 287 | if k:sub(1,1) ~= "_" and v._mandatory then |
| 244 | if mandatory_key then | 288 | if not tbl[k] then |
| 245 | if not tbl[mandatory_key] then | 289 | return nil, "Mandatory field "..mkfield(context, k).." is missing." |
| 246 | return nil, "Mandatory field "..context..mandatory_key.." is missing." | ||
| 247 | end | 290 | end |
| 248 | end | 291 | end |
| 249 | end | 292 | end |
| 250 | return true | 293 | return true |
| 251 | end | 294 | end |
| 252 | 295 | ||
| 253 | local function check_undeclared_globals(globals, types) | 296 | local function check_undeclared_globals(globals, typetbl) |
| 254 | local undeclared = {} | 297 | local undeclared = {} |
| 255 | for glob, _ in pairs(globals) do | 298 | for glob, _ in pairs(globals) do |
| 256 | if not (types[glob] or types["MUST_"..glob]) then | 299 | if not (typetbl[glob] or typetbl["MUST_"..glob]) then |
| 257 | table.insert(undeclared, glob) | 300 | table.insert(undeclared, glob) |
| 258 | end | 301 | end |
| 259 | end | 302 | end |
| @@ -273,13 +316,12 @@ end | |||
| 273 | -- succeeded, or nil and an error message if it failed. | 316 | -- succeeded, or nil and an error message if it failed. |
| 274 | function type_check.type_check_rockspec(rockspec, globals) | 317 | function type_check.type_check_rockspec(rockspec, globals) |
| 275 | assert(type(rockspec) == "table") | 318 | assert(type(rockspec) == "table") |
| 276 | if rockspec.rockspec_format then | 319 | if not rockspec.rockspec_format then |
| 277 | -- relies on global state | 320 | rockspec.rockspec_format = "1.0" |
| 278 | type_check.load_extensions() | ||
| 279 | end | 321 | end |
| 280 | local ok, err = check_undeclared_globals(globals, rockspec_types) | 322 | local ok, err = check_undeclared_globals(globals, rockspec_types) |
| 281 | if not ok then return nil, err end | 323 | if not ok then return nil, err end |
| 282 | return type_check_table(rockspec, rockspec_types, "") | 324 | return type_check_table(rockspec.rockspec_format, rockspec, rockspec_types, "") |
| 283 | end | 325 | end |
| 284 | 326 | ||
| 285 | --- Type check a manifest table. | 327 | --- Type check a manifest table. |
| @@ -292,7 +334,7 @@ function type_check.type_check_manifest(manifest, globals) | |||
| 292 | assert(type(manifest) == "table") | 334 | assert(type(manifest) == "table") |
| 293 | local ok, err = check_undeclared_globals(globals, manifest_types) | 335 | local ok, err = check_undeclared_globals(globals, manifest_types) |
| 294 | if not ok then return nil, err end | 336 | if not ok then return nil, err end |
| 295 | return type_check_table(manifest, manifest_types, "") | 337 | return type_check_table("1.0", manifest, manifest_types, "") |
| 296 | end | 338 | end |
| 297 | 339 | ||
| 298 | return type_check | 340 | return type_check |
