aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHisham Muhammad <hisham@gobolinux.org>2014-09-09 23:05:59 -0300
committerHisham Muhammad <hisham@gobolinux.org>2014-09-09 23:05:59 -0300
commitf0d66aeacd60e6df926a55ecb9a08e6f802f9242 (patch)
tree9f8a83fe0756a4d7103e1fe9513f04887d05cfd4
parent0587afbb5fe8ceb2f2eea16f486bd6183bf02f29 (diff)
downloadluarocks-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.lua274
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 = {}
6package.loaded["luarocks.type_check"] = type_check 6package.loaded["luarocks.type_check"] = type_check
7 7
8local cfg = require("luarocks.cfg") 8local cfg = require("luarocks.cfg")
9local deps = require("luarocks.deps")
9 10
10type_check.rockspec_format = "1.0" 11type_check.rockspec_format = "1.1"
12
13local string_1 = { _type = "string" }
14local 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
12local rockspec_types = { 30local 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
85function type_check.load_extensions() 108rockspec_types.build.platforms._any = rockspec_types.build
86 type_check.rockspec_format = "1.1" 109rockspec_types.dependencies.platforms._any = rockspec_types.dependencies
87 rockspec_types.deploy = { 110rockspec_types.external_dependencies.platforms._any = rockspec_types.external_dependencies
88 wrap_bin_scripts = true, 111rockspec_types.source.platforms._any = rockspec_types.source
89 } 112rockspec_types.hooks.platforms._any = rockspec_types.hooks
90end
91
92if cfg.use_extensions then
93 type_check.load_extensions()
94end
95
96rockspec_types.build.platforms.ANY = rockspec_types.build
97rockspec_types.dependencies.platforms.ANY = rockspec_types.dependencies
98rockspec_types.external_dependencies.platforms.ANY = rockspec_types.external_dependencies
99rockspec_types.MUST_source.platforms.ANY = rockspec_types.MUST_source
100rockspec_types.hooks.platforms.ANY = rockspec_types.hooks
101 113
102local manifest_types = { 114local 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
172local 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
182end
183
157local type_check_table 184local 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
173local function type_check_item(name, item, expected, context) 198local 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
203end 232end
204 233
234local function mkfield(context, field)
235 if context == "" then
236 return field
237 end
238 return context.."."..field
239end
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.
226type_check_table = function(tbl, types, context) 263type_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
251end 294end
252 295
253local function check_undeclared_globals(globals, types) 296local 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.
274function type_check.type_check_rockspec(rockspec, globals) 317function 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, "")
283end 325end
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, "")
296end 338end
297 339
298return type_check 340return type_check