]> Dogcows Code - chaz/yoink/blob - build/config.lua
build system enhancements
[chaz/yoink] / build / config.lua
1 --
2 -- Yoink Build System
3 -- Execute this script with Lua to prepare the project for compiling.
4 --
5 -- Copyright (c) 2011, Charles McGarvey
6 -- Distributable under the terms and conditions of the 2-clause BSD
7 -- license; see the file COPYING for a complete text of the license.
8 --
9
10
11 --local function trace(event, line)
12 --local s = debug.getinfo(2).short_src
13 --print(s .. ":" .. line)
14 --end
15 --debug.sethook(trace, "l")
16
17
18 -- Convert the arguments into standard form. Each argument should be a
19 -- string. A table is returned with the arguments' standard form as keys.
20 -- If an argument has a value, it will correspond to the appropriate key in
21 -- the table. This function supports autoconf-style arguments, including:
22 -- 1. -h, --help produce a --help key with a value of true.
23 -- 2. --enable-feature=[yes|no|whatever], --disable-feature produce a
24 -- --enable-feature key with a value of true, false, or a string.
25 -- 3. --with-package=[yes|no|whatever], --without-feature produce a
26 -- --with-package key with a value of true, false, or a string.
27 -- 4. SYMBOL=VALUE produce a SYMBOL key with VALUE as the string value.
28 -- 5. Anything else will appear as a key in the table as-is with a value of
29 -- true.
30 -- If multiple arguments mapping to the same key are present in the
31 -- argument list, the last one is used.
32 local function parseArguments(...)
33 local result = {}
34 local filters = {}
35
36 local function setPair(key, value) result[key] = value end
37 local function setTrue(key) result[key] = true end
38 local function addLibrarySearchPath(path)
39 package.path = path .. "/?.lua;" .. package.path
40 package.cpath = path .. "/?.so;" .. package.cpath
41 end
42
43 table.insert(filters, {"^-h$", function() result["--help"] = true end})
44 table.insert(filters, {"^-L(.*)$", addLibrarySearchPath})
45
46 table.insert(filters, {"^(--enable%-[%w_-]+)=?(.*)$", setPair})
47 table.insert(filters, {"^--disable%-([%w_-]+)$", function(feature)
48 setPair("--enable-" .. feature, "no")
49 end})
50
51 table.insert(filters, {"^(--with%-[%w_-]+)=?(.*)$", setPair})
52 table.insert(filters, {"^--without%-([%w_-]+)$", function(package)
53 setPair("--with-" .. package, "no")
54 end})
55
56 table.insert(filters, {"^([%w_-]+)=(.*)$", setPair})
57 table.insert(filters, {"^([%w_-]+)$", setTrue})
58
59 for _,a in ipairs(arg)
60 do
61 for _,filter in pairs(filters)
62 do
63 local matches = {a:match(filter[1])}
64 if matches[1] then filter[2](unpack(matches)) break end
65 end
66 end
67
68 return result
69 end
70
71
72 local arguments = parseArguments(unpack(arg))
73 local interactive = arguments["--interactive"]
74 local rules = arguments["--rules"] or "options.lua"
75 local configfile = arguments["--configfile"] or "config.mk"
76 local printendmsg = arguments["--print-instructions"]
77 local exportHeader = arguments["--export-header"]
78 local exportTerms = arguments["--export-terms"]
79
80
81 local util = require "utility"
82 util.logfile = "config.log"
83
84
85 local printInfo = util.printer("%s - %s\n")
86 local printWarning = util.printer("\a%s - %s\n", "33")
87 local printError = util.printer("\a%s - %s\n", "31")
88 local function beginProcess(title, caption)
89 print(string.format("%s:\n", title))
90 local updater = util.printer(" [%3d%%] %s\n")
91 updater(0, caption)
92 return function(progress, caption)
93 if progress ~= nil
94 then
95 if 0.0 <= progress and progress <= 1.0 then progress = progress * 100 end
96 return updater(progress, caption)
97 end
98 print()
99 return 0
100 end
101 end
102
103 local function loadDialog(name, configfile)
104 local dialog
105 local result, err = pcall(function() dialog = require "dialog" end)
106 if not result
107 then
108 printWarning(err)
109 return nil
110 end
111 dialog.title = string.format("%s - %s Configuration", configfile, name)
112 printInfo = dialog.msgbox
113 printWarning = dialog.msgbox
114 printError = dialog.msgbox
115 beginProcess = dialog.gauge
116 return dialog
117 end
118
119
120 local function topologicalSort(lookup)
121 local dependants = util.copy(lookup, "v", {v = function() return 0 end})
122 for _,option in pairs(lookup)
123 do
124 for _,dep in ipairs(option:getDirectDependants())
125 do
126 dependants[dep] = dependants[dep] + 1
127 end
128 end
129
130 local sorted = {}
131 local queued = {}
132 for symbol,count in pairs(dependants)
133 do
134 if count == 0 then table.insert(queued, symbol) end
135 end
136 while 0 < #queued
137 do
138 local symbol = table.remove(queued)
139 table.insert(sorted, symbol)
140 for _,dep in ipairs(lookup[symbol]:getDirectDependants())
141 do
142 dependants[dep] = dependants[dep] - 1
143 if dependants[dep] == 0 then table.insert(queued, dep) end
144 end
145 end
146
147 local remaining = {}
148 for symbol,count in pairs(dependants)
149 do
150 if 0 < count then table.insert(remaining, symbol) end
151 end
152 if 0 < #remaining
153 then
154 printWarning("Q.A. Notice", "One or more circular dependencies were detected involving these symbols: "
155 .. table.concat(remaining, ", "))
156 for _,symbol in ipairs(remaining) do table.insert(sorted, symbol) end
157 end
158
159 return sorted
160 end
161
162 local function checkSymbols(symbols)
163 local check = topologicalSort(symbols)
164 local isErr = false
165 local updateTask = function() end
166 if 1 < #check
167 then
168 updateTask = beginProcess("Checking Symbols", "Resolving dependencies...", 6, 65)
169 end
170 for i = 1, #check
171 do
172 local option = symbols[check[i]]
173 if option:validate() == nil then isErr = true end
174 updateTask(i / #check,
175 (option:getSymbol() .. ": " .. option:toExpandedString()):truncate(60))
176 end
177 updateTask()
178 if isErr then return nil end
179 return true
180 end
181
182
183 ---------------------------------------------------------------------------
184 local Option = util.class()
185 ---------------------------------------------------------------------------
186
187 function Option:__init(rule, lookup, objects)
188 self.name = rule.name
189 self.caption = rule.caption
190 self.help = rule.help
191 self.value = rule.value
192 self.symbol = rule.symbol
193 self.cmdline = rule.cmdline
194 self.config = rule.config
195 self.export = rule.export
196 self.visible = rule.visible
197 self.check = rule.check
198 self.temp = rule.temp
199 self.before = rule.before
200 self.after = rule.after
201 self.lookup = lookup or {}
202 self.objects = objects or {}
203
204 if type(self.check) == "function" then setfenv(self.check, self.lookup) end
205 if type(self.visible) == "function" then setfenv(self.visible, self.lookup) end
206
207 if self.symbol ~= nil
208 then
209 if self.lookup[self.symbol] ~= nil
210 then
211 printWarning("duplicate symbol defined:", self.symbol)
212 end
213 self.lookup[self.symbol] = self.value
214 self.objects[self.symbol] = self
215 end
216 end
217
218 -- Get the symbol of the option. The symbol is used as a reference in the
219 -- string values of other options. It must be unique.
220 function Option:getSymbol()
221 return self.symbol
222 end
223
224 -- Get the argument of the option.
225 function Option:getArg()
226 return self.cmdline
227 end
228
229 -- Get the value of the option as a string.
230 function Option:toString()
231 return tostring(self.lookup[self.symbol])
232 end
233
234 -- Get the value of the option as an expanded string. For most types of
235 -- options, this is the same as Option:toString().
236 function Option:toExpandedString()
237 return self:toString()
238 end
239
240 function Option:getDirectDependants()
241 return self.before or {}
242 end
243
244 function Option:getUnsortedDependants(dependants)
245 dependants = dependants or {}
246 if dependants[self.symbol] then return dependants end
247 dependants[self.symbol] = self
248 if self.before
249 then
250 for _,dep in ipairs(self.before)
251 do
252 local option = self.objects[dep]
253 if option
254 then
255 dependants = option:getUnsortedDependants(dependants)
256 else
257 printWarning("invalid dependency: " .. dep)
258 end
259 end
260 end
261 return dependants
262 end
263
264 function Option:addDependant(dependency)
265 self.before = self.before or {}
266 table.insert(self.before, dependency)
267 end
268
269 function Option:convertDependenciesToDependants()
270 if self.after
271 then
272 local dep = table.remove(self.after)
273 while dep ~= nil
274 do
275 local option = self.objects[dep]
276 if option
277 then
278 option:addDependant(self.symbol)
279 else
280 printWarning("invalid dependency: " .. dep)
281 end
282 dep = table.remove(self.after)
283 end
284 end
285 end
286
287 function Option:getObjects()
288 return self.objects
289 end
290
291 -- Check the value of the option for validity.
292 -- Returns a valid value based on the given value, or nil (with an error
293 -- message) if the value is invalid and could not be converted to a valid
294 -- value.
295 function Option:validate(value)
296 local err
297 if value == nil then value = self.lookup[self.symbol] end
298 self.problem = nil
299 if type(self.check) == "function"
300 then
301 value, err = self.check(value)
302 if value == nil then self.problem = err return nil, err end
303 end
304 self.lookup[self.symbol] = value
305 return value, err
306 end
307
308 -- Set the value of the option to the value in a new lookup table.
309 function Option:applyTable(lookup)
310 end
311
312 -- Set the value of the option based on a table of argument flags.
313 function Option:applyArguments()
314 end
315
316 -- Set the value of the option.
317 function Option:set(value)
318 self.lookup[self.symbol] = value
319 if checkSymbols(self:getUnsortedDependants())
320 then
321 return self.lookup[self.symbol]
322 end
323 end
324
325 -- Write the symbol and current value to the config file.
326 function Option:writeSymbol(fd)
327 if self.symbol ~= nil and not self.temp
328 then
329 fd:write(util.align(self.symbol, "= " .. tostring(self.lookup[self.symbol]), 16) .. "\n")
330 --fd:write(string.format("%s\t= %s\n", self.symbol, tostring(self.lookup[self.symbol])))
331 end
332 end
333
334 -- Write the option value to the config header file.
335 function Option:writeCppLine(fd)
336 end
337
338 -- Write the option value as a string replacement to the sed file.
339 function Option:writeSedLine(fd)
340 if self.export
341 then
342 local value = self:toExpandedString():gsub("/", "\\/")
343 local function writeLine(key)
344 key = tostring(key):gsub("/", "\\/")
345 fd:write(string.format("s/@%s@/%s/g\n", key, value))
346 end
347 if type(self.export) == "table"
348 then
349 for _,key in ipairs(self.export) do writeLine(key) end
350 else
351 writeLine(self.export)
352 end
353 end
354 end
355
356 -- Run an interactive menu with the given dialog context. The type of menu
357 -- run will depend on the type of option.
358 function Option:showMenu(dialog)
359 end
360
361 -- Get whether or not there is a problem with the current value of the
362 -- option.
363 function Option:isValid()
364 return type(self.problem) ~= "string"
365 end
366
367 -- Get a human-readable description of the problem(s) this option faces
368 -- before it can be considered valid.
369 function Option:getProblem()
370 return self.problem
371 end
372
373 -- Get the name to be used for this option in the interactive menu.
374 function Option:getMenuItem()
375 if not self:isValid() then return self.name .. " <!>" end
376 return self.name
377 end
378
379 -- Get the label used to represent this option in a menu.
380 function Option:getLabel()
381 return ""
382 end
383
384 -- Get whether or not this option should be visible in a menu of options.
385 -- Returns true if option is visible, false otherwise.
386 function Option:isVisible()
387 if type(self.visible) == "function" then return self.visible() end
388 return true
389 end
390
391 -- If the option has an argument flag, print out a line with information
392 -- about the purpose of this option.
393 function Option:printHelpLine()
394 if self.cmdline ~= nil
395 then
396 print(util.align(" " .. self:getArg(), self.caption, 32))
397 end
398 end
399
400 function Option:getHelpText()
401 local value = self:toString()
402 local help = self.help or "No help available.\n"
403 local name = self.name or "Unnamed"
404 local symbol = self:getSymbol() or "None"
405 local arg = self:getArg() or "None"
406
407 local problem = self:getProblem()
408 if problem then problem = "\nProblem(s):\n" .. problem
409 else problem = "" end
410
411 return [[
412
413 ]] .. help .. [[
414
415 Option Name: ]] .. name .. [[
416
417 Symbol: ]] .. symbol .. [[
418
419 Current Value: ]] .. value .. [[
420
421 Argument: ]] .. arg .. [[
422
423 ]] .. problem
424 end
425
426
427 ---------------------------------------------------------------------------
428 local StringOption = util.class(Option)
429 ---------------------------------------------------------------------------
430
431 -- Get the expanded option value. Symbols which begin with dollar signs
432 -- will be substituted for their values.
433 function StringOption:toExpandedString()
434 local function expand(value)
435 _, value, count = pcall(string.gsub, value, "%$%(([%w_]+)%)", self.lookup)
436 if not count then count = 0 end
437 return value, count
438 end
439 local value = self.lookup[self.symbol]
440 local iterations = 0
441 while iterations < 8
442 do
443 local count = 0
444 value, count = expand(value)
445 if count == 0 then return value end
446 iterations = iterations + 1
447 end
448 return value
449 end
450
451 function StringOption:applyTable(lookup)
452 local value = lookup[self.symbol]
453 if type(value) == "string" then self.lookup[self.symbol] = tostring(value) end
454 end
455
456 function StringOption:applyArguments(args)
457 local value = args[self.cmdline]
458 if value ~= nil then self:validate(tostring(value)) end
459 end
460
461 function StringOption:writeCppLine(fd)
462 if self.config
463 then
464 local value = self:toExpandedString()
465 local function writeLine(key)
466 if type(self.value) == "string"
467 then
468 fd:write(string.format("#define %s %q\n", key, value))
469 else
470 fd:write(string.format("#define %s %s\n", key, value))
471 end
472 end
473 if type(self.config) == "table"
474 then
475 for _,key in ipairs(self.config) do writeLine(key) end
476 else
477 writeLine(self.config)
478 end
479 end
480 end
481
482 function StringOption:showMenu(dialog)
483 local code, item = dialog.inputbox(self.name, self.caption, self.lookup[self.symbol])
484 if code == 0 and item ~= self.lookup[self.symbol]
485 then
486 return self:set(item)
487 end
488 end
489
490 function StringOption:getLabel()
491 return self:toExpandedString()
492 end
493
494
495 ---------------------------------------------------------------------------
496 local EnumOption = util.class(StringOption)
497 ---------------------------------------------------------------------------
498
499 -- An enumeration option is like a string, but it can only hold certain
500 -- pre-determined values. In a rule, it is distinguished by its value key
501 -- which refers to an array of the possible (string) values.
502 function EnumOption:__init(rule, lookup, objects)
503 Option.__init(self, rule, lookup, objects)
504 self.lookup[self.symbol] = self.value[1]
505 end
506
507 function EnumOption:getArg()
508 if self.cmdline == nil then return nil end
509 return self.cmdline .. "=[" .. table.concat(self.value, "|") .. "]"
510 end
511
512 function EnumOption:validate(str)
513 str = str or self.lookup[self.symbol]
514 for _,value in ipairs(self.value)
515 do
516 if value == str then return Option.validate(self, str) end
517 end
518 self.problem = "The assigned value (" .. str .. ") is not a valid option."
519 return nil, self.problem
520 end
521
522 function EnumOption:applyArguments(args)
523 -- Just like applying arguments for strings, but if the argument is
524 -- false, assume the first value should be assigned.
525 local value = args[self.cmdline]
526 if value == false then self:validate(self.value[1]) return end
527 StringOption.applyArguments(self, args)
528 end
529
530 function EnumOption:writeCppLine(fd)
531 if self.config
532 then
533 local value = self:toString():upper()
534 local function writeLine(key)
535 key = tostring(key) .. "_" .. value
536 fd:write(string.format("#define %s 1\n", key))
537 end
538 if type(self.config) == "table"
539 then
540 for _,key in ipairs(self.config) do writeLine(key) end
541 else
542 writeLine(self.config)
543 end
544 end
545 end
546
547 function EnumOption:showMenu(dialog)
548 local menuitems = {}
549 for _,value in ipairs(self.value)
550 do
551 table.insert(menuitems, {value, ""})
552 end
553 local code, item = dialog.menu(
554 self.name,
555 self.caption,
556 menuitems,
557 self.lookup[self.symbol],
558 {["--ok-label"] = "Select"}
559 )
560 if code == 0 and item ~= self.lookup[self.symbol]
561 then
562 return self:set(item)
563 end
564 end
565
566 function EnumOption:getLabel()
567 return "[" .. StringOption.getLabel(self) .. "]"
568 end
569
570
571 ---------------------------------------------------------------------------
572 local BooleanOption = util.class(Option)
573 ---------------------------------------------------------------------------
574
575 function BooleanOption:toString()
576 if self.lookup[self.symbol] then return "Yes" else return "No" end
577 end
578
579 function BooleanOption:applyTable(lookup)
580 local value = lookup[self.symbol]
581 if type(value) == "boolean" then self.lookup[self.symbol] = value end
582 end
583
584 function BooleanOption:applyArguments(args)
585 local value = args[self.cmdline]
586 if value == "yes" or value == "" then value = true end
587 if value == "no" then value = false end
588 if type(value) == "boolean" then self:validate(value) end
589 end
590
591 function BooleanOption:writeCppLine(fd)
592 if self.config
593 then
594 local value = self.lookup[self.symbol]
595 local function writeLine(key)
596 -- Reverse the value if key starts with a bang.
597 local value = value
598 if key:byte(1) == 33 then key = key:sub(2) value = not value end
599 if value
600 then
601 fd:write("#define " .. key .. " 1\n")
602 else
603 fd:write("/* #undef " .. key .. " */\n")
604 end
605 end
606 if type(self.config) == "table"
607 then
608 for _,key in ipairs(self.config) do writeLine(key) end
609 else
610 writeLine(self.config)
611 end
612 end
613 end
614
615 function BooleanOption:showMenu(dialog)
616 local item = not self.lookup[self.symbol]
617 return self:set(item)
618 end
619
620 function BooleanOption:getLabel()
621 if self.lookup[self.symbol] then return "[X]" else return "[ ]" end
622 end
623
624
625 ---------------------------------------------------------------------------
626 local NullOption = util.class(Option)
627 ---------------------------------------------------------------------------
628
629 -- Null options don't really hold any useful information; they just
630 -- translate to a blank line in the menuconfig. Any non-table object in a
631 -- rule will become this type of option.
632 function NullOption:__init()
633 self.name = ""
634 self.lookup = {}
635 end
636
637 function NullOption:validate()
638 return true
639 end
640 function NullOption:isVisible()
641 return true
642 end
643
644
645 ---------------------------------------------------------------------------
646 local GroupOption = util.class(Option)
647 ---------------------------------------------------------------------------
648
649 local function optionFactory(rule, lookup, objects)
650 local jump = setmetatable({
651 string = StringOption,
652 number = StringOption,
653 table = EnumOption,
654 boolean = BooleanOption
655 }, {
656 __index = function() return GroupOption end
657 })
658 if type(rule) == "table"
659 then
660 return jump[type(rule.value)](rule, lookup, objects)
661 else
662 -- If the rule is not a table, just insert a placeholder option for
663 -- a blank line.
664 return NullOption()
665 end
666 end
667
668 -- A GroupOption is not really an option itself, but a group of options.
669 function GroupOption:__init(rule, lookup, objects)
670 Option.__init(self, rule, lookup, objects)
671 self.children = {}
672
673 for _,child in ipairs(rule)
674 do
675 table.insert(self.children, optionFactory(child, self.lookup, self.objects))
676 end
677 end
678
679 function GroupOption:toString()
680 return "n/a"
681 end
682
683 -- Call the method with an arbitrary number of arguments to each
684 -- sub-option.
685 function GroupOption:recurse(method, ...)
686 for _,child in ipairs(self.children)
687 do
688 child[method](child, unpack(arg))
689 end
690 end
691
692 function GroupOption:convertDependenciesToDependants()
693 self:recurse("convertDependenciesToDependants")
694 end
695
696 -- Validate each sub-option in order. The validation will short-circuit
697 -- and return nil (with an error message) upon the first failed validation.
698 function GroupOption:validate()
699 for _,child in ipairs(self.children)
700 do
701 local result, err = child:validate()
702 if result == nil then return result, err end
703 end
704 return true
705 end
706
707 function GroupOption:isValid()
708 for _,child in ipairs(self.children)
709 do
710 if not child:isValid() then return false end
711 end
712 return true
713 end
714
715 function GroupOption:getProblem()
716 local problems = ""
717 for _,child in ipairs(self.children)
718 do
719 local problem = child:getProblem()
720 if problem
721 then
722 local symbol = child:getSymbol()
723 if symbol
724 then
725 problems = problems .. util.align(symbol .. ":", problem, 16) .. "\n"
726 else
727 problems = problems .. problem .. "\n"
728 end
729 util.align(symbol, problem, 16)
730 end
731 end
732 if problems == "" then return nil end
733 return util.trim(problems)
734 end
735
736 function GroupOption:applyTable(lookup)
737 self:recurse("applyTable", lookup)
738 end
739
740 function GroupOption:applyArguments(args)
741 self:recurse("applyArguments", args)
742 end
743
744 function GroupOption:writeSymbol(fd)
745 if self.name ~= nil then
746 fd:write(string.format("\n# %s\n", self.name))
747 self:recurse("writeSymbol", fd)
748 end
749 end
750
751 function GroupOption:writeCppLine(fd)
752 self:recurse("writeCppLine", fd)
753 end
754
755 function GroupOption:writeSedLine(fd)
756 self:recurse("writeSedLine", fd)
757 end
758
759
760 function GroupOption:showMenu(dialog)
761 local running, dirty, selected = true
762 while running
763 do
764 local menuitems = {}
765 for _,value in ipairs(self.children)
766 do
767 if type(value) ~= "table"
768 then
769 table.insert(menuitems, value)
770 elseif type(value.name) == "string" and value:isVisible()
771 then
772 local name = value:getMenuItem()
773 local label = value:getLabel()
774 local caption = ""
775 if type(value.caption) == "string"
776 then
777 caption = value.caption
778 menuitems["HELP " .. value.caption] = value
779 end
780 menuitems[name] = value
781 table.insert(menuitems, {name, label, caption})
782 end
783 end
784
785 local code, item = dialog.menu(
786 self.name,
787 self.caption,
788 menuitems,
789 selected,
790 {
791 ["--ok-label"] = "Select",
792 ["--cancel-label"] = "Exit",
793 ["--item-help"] = true,
794 ["--help-button"] = true
795 }
796 )
797
798 if code == 0
799 then
800 local value = menuitems[item]
801 if type(value) == "table"
802 then
803 if value:showMenu(dialog) ~= nil then dirty = "changed" end
804 selected = value:getMenuItem()
805 else
806 selected = value
807 end
808 elseif code == 2
809 then
810 local value = menuitems[item]
811 if value
812 then
813 dialog.msgbox(value.name, value:getHelpText(), {["--no-collapse"] = true})
814 selected = value:getMenuItem()
815 else
816 dialog.msgbox("No Help",
817 "Sorry, no help is available for this option.")
818 end
819 else
820 running = false
821 end
822 end
823 return dirty
824 end
825
826 function GroupOption:getLabel()
827 return "-->"
828 end
829
830 function GroupOption:printHelpLine()
831 if self:isVisible() and self.name ~= nil then
832 print(string.format("\n%s:", self.name))
833 self:recurse("printHelpLine")
834 end
835 end
836
837
838 -- Print arguments and usage information for all of the sub-options.
839 function GroupOption:printHelp(name)
840 print([[
841
842 This script prepares ]] .. name .. [[ for building on your system.
843 Usage: ./configure [OPTION]... [VAR=VALUE]...]])
844
845 self:printHelpLine()
846 print()
847 end
848
849 -- Set values of the sub-options based on a table that is loaded from a
850 -- file. The table is formatted as one or more lines with keys and values
851 -- separated by an equal sign. The pound sign (#) serves to start a
852 -- comment on the line. The first equal sign on a line is used as the
853 -- separator, so keys cannot have an equal sign.
854 function GroupOption:loadFromFile(filepath)
855 local lookup = {}
856 local fd = io.open(filepath)
857 if fd
858 then
859 for line in fd:lines()
860 do
861 if not line:find("^%s*#")
862 then
863 key, value = line:match("^%s*(%S.-)%s*=%s*(.*)%s*")
864 if value
865 then
866 if value:upper() == "TRUE" then value = true
867 elseif value:upper() == "FALSE" then value = false end
868 end
869 if key then lookup[key] = value end
870 end
871 end
872 fd:close()
873 end
874 self:applyTable(lookup)
875 end
876
877 -- Save a table with the current values of the sub-options to a file. The
878 -- file can be reloaded with GroupOption:loadFromFile.
879 function GroupOption:saveToFile(filepath)
880 local fd = io.open(filepath, "w")
881 if fd
882 then
883 fd:write(string.format("\n# Auto-generated: %s", os.date()))
884 self:writeSymbol(fd)
885 fd:write("\n")
886 else
887 printError("couldn't save config file to:", filepath)
888 end
889 end
890
891 -- Exports the config header file. The key-value pairs of sub-options with
892 -- "config" defined will be written to the file.
893 function GroupOption:exportHeader(filepath)
894 local fd = io.open(filepath, "w")
895 self:writeCppLine(fd)
896 fd:close()
897 end
898
899 -- Exports the search 'n replace file. The key-value pairs of sub-options
900 -- with "export" defined will be written to the file.
901 function GroupOption:exportTerms(filepath)
902 local fd = io.open(filepath, "w")
903 self:writeSedLine(fd)
904 fd:close()
905 end
906
907 -- Run menuconfig. This is different from showMenu in that this method
908 -- tracks changes and returns an action: save, cancel, nochange or error.
909 function GroupOption:runMenu()
910 local result, dirty = nil, false
911 while result == nil
912 do
913 if self:showMenu(dialog) ~= nil then dirty = true end
914 if not self:isValid()
915 then
916 local code = dialog.yesno("Oh drat!",
917 "There is at least one problem with the configuration, marked with <!>, and the configuration will not be saved. Do you really want to exit without saving?",
918 {
919 ["--extra-button"] = false,
920 ["--ok-label"] = "Exit",
921 ["--cancel-label"] = "Back"
922 })
923 if code == 0 or code == 255 then result = "error" end
924 elseif dirty
925 then
926 local code = dialog.yesno("Save Changes?",
927 "Your configuration has been altered. Do you want to save the changes?",
928 {
929 ["--ok-label"] = "Save",
930 ["--cancel-label"] = "Back",
931 ["--extra-button"] = true,
932 ["--extra-label"] = "Exit"
933 })
934 if code == 0 then result = "save"
935 elseif code == 3 or code == 255 then result = "cancel" end
936 else
937 result = "nochange"
938 end
939 end
940 return result
941 end
942
943
944 local loadFn = assert(loadfile(rules))
945 local name, rules = loadFn()
946
947 local options = optionFactory(rules)
948 if arguments["--help"] then options:printHelp(name) os.exit() end
949
950 options:loadFromFile(configfile)
951 if exportHeader or exportTerms
952 then
953 if exportHeader then options:exportHeader(exportHeader) end
954 if exportTerms then options:exportTerms(exportTerms) end
955 os.exit()
956 end
957
958 print()
959 printInfo(name, "Preparing for building and installation on your system.\n")
960 options:applyArguments(arguments)
961
962 if interactive then loadDialog(name, configfile) end
963
964 options:convertDependenciesToDependants()
965 checkSymbols(options:getObjects())
966
967 if dialog
968 then
969 local action = options:runMenu()
970 if action == "exit" or action == "error"
971 then
972 print("configuration aborted by user request")
973 os.exit(1)
974 else
975 options:saveToFile(configfile)
976 print("configuration saved to " .. configfile)
977 end
978 elseif options:isValid()
979 then
980 options:saveToFile(configfile)
981 else
982 printError("Uh oh!", [[
983 There is at least one unresolved problem with the configuration.
984 ]] .. options:getProblem() .. "\n")
985 os.exit(2)
986 end
987
988 if printendmsg ~= "no"
989 then
990 printInfo("Configuration complete!", [[
991 To finish the installation, type:
992 make
993 make install
994 ]])
995 end
996
997 -- vi:ts=4 sw=4 tw=75
998
This page took 0.080759 seconds and 4 git commands to generate.