1-- 2-- Copyright (c) 2023 Kyle Evans <kevans@FreeBSD.org> 3-- 4-- SPDX-License-Identifier: BSD-2-Clause 5-- 6 7local libgen = require('posix.libgen') 8local lfs = require('lfs') 9local stdlib = require('posix.stdlib') 10local unistd = require('posix.unistd') 11local sys_wait = require('posix.sys.wait') 12 13local curdate = os.date("%B %e, %Y") 14 15local output_head <const> = ".\\\" DO NOT EDIT-- this file is @" .. [[generated by tools/build/options/makeman. 16.Dd ]] .. curdate .. [[ 17 18.Dt SRC.CONF 5 19.Os 20.Sh NAME 21.Nm src.conf 22.Nd "source build options" 23.Sh DESCRIPTION 24The 25.Nm 26file contains variables that control what components will be generated during 27the build process of the 28.Fx 29source tree; see 30.Xr build 7 . 31.Pp 32The 33.Nm 34file uses the standard makefile syntax. 35However, 36.Nm 37should not specify any dependencies to 38.Xr make 1 . 39Instead, 40.Nm 41is to set 42.Xr make 1 43variables that control the aspects of how the system builds. 44.Pp 45The default location of 46.Nm 47is the top level of the source tree, or 48.Pa /etc/src.conf 49if no 50.Nm 51is found in the source tree itself, 52though an alternative location can be specified in the 53.Xr make 1 54variable 55.Va SRCCONF . 56Overriding the location of 57.Nm 58may be necessary if the system-wide settings are not suitable 59for a particular build. 60For instance, setting 61.Va SRCCONF 62to 63.Pa /dev/null 64effectively resets all build controls to their defaults. 65.Pp 66The only purpose of 67.Nm 68is to control the compilation of the 69.Fx 70source code, which is usually located in 71.Pa /usr/src . 72As a rule, the system administrator creates 73.Nm 74when the values of certain control variables need to be changed 75from their defaults. 76.Pp 77In addition, control variables can be specified 78for a particular build via the 79.Fl D 80option of 81.Xr make 1 82or in its environment; see 83.Xr environ 7 . 84.Pp 85The environment of 86.Xr make 1 87for the build can be controlled via the 88.Va SRC_ENV_CONF 89variable, which defaults to 90.Pa /etc/src-env.conf . 91Some examples that may only be set in this file are 92.Va WITH_DIRDEPS_BUILD , 93and 94.Va WITH_META_MODE , 95and 96.Va MAKEOBJDIRPREFIX 97as they are environment-only variables. 98.Pp 99The values of 100.Va WITH_ 101and 102.Va WITHOUT_ 103variables are ignored regardless of their setting; 104even if they would be set to 105.Dq Li FALSE 106or 107.Dq Li NO . 108The presence of an option causes 109it to be honored by 110.Xr make 1 . 111.Pp 112This list provides a name and short description for variables 113that can be used for source builds. 114.Bl -tag -width indent 115]] 116 117local output_tail <const> = [[.El 118.Sh FILES 119.Bl -tag -compact -width Pa 120.It Pa /etc/src.conf 121.It Pa /etc/src-env.conf 122.It Pa /usr/share/mk/bsd.own.mk 123.El 124.Sh SEE ALSO 125.Xr make 1 , 126.Xr make.conf 5 , 127.Xr build 7 , 128.Xr ports 7 129.Sh HISTORY 130The 131.Nm 132file appeared in 133.Fx 7.0 . 134.Sh AUTHORS 135This manual page was autogenerated by 136.An tools/build/options/makeman . 137]] 138 139local scriptdir <const> = libgen.dirname(stdlib.realpath(arg[0])) 140local srcdir <const> = stdlib.realpath(scriptdir .. "/../../../") 141local makesysdir <const> = srcdir .. "/share/mk" 142 143local make_envvar = os.getenv("MAKE") 144local make_cmd_override = {} 145if make_envvar then 146 for word in make_envvar:gmatch("[^%s]+") do 147 make_cmd_override[#make_cmd_override + 1] = word 148 end 149end 150 151-- Lifted from bsdinstall/scripts/pkgbase.in (read_all) 152local function read_pipe(pipe) 153 local ret = "" 154 repeat 155 local buffer = assert(unistd.read(pipe, 1024)) 156 ret = ret .. buffer 157 until buffer == "" 158 return ret 159end 160local function run_make(args) 161 local cmd_args = {"env", "-i", "make", "-C", srcdir, "-m", makesysdir, 162 "__MAKE_CONF=/dev/null", "SRCCONF=/dev/null"} 163 164 if #make_cmd_override > 0 then 165 cmd_args[3] = make_cmd_override[1] 166 for k = 2, #make_cmd_override do 167 local val = make_cmd_override[k] 168 169 table.insert(cmd_args, 3 + (k - 1), val) 170 end 171 end 172 for k, v in ipairs(args) do 173 cmd_args[#cmd_args + 1] = v 174 end 175 176 local r, w = assert(unistd.pipe()) 177 local pid = assert(unistd.fork()) 178 if pid == 0 then 179 -- Child 180 assert(unistd.close(r)) 181 assert(unistd.dup2(w, 1)) 182 assert(unistd.dup2(w, 2)) 183 assert(unistd.execp("env", cmd_args)) 184 unistd._exit() 185 end 186 187 -- Parent 188 assert(unistd.close(w)) 189 190 local output = read_pipe(r) 191 assert(unistd.close(r)) 192 193 local _, exit_type, exit_code = assert(sys_wait.wait(pid)) 194 assert(exit_type == "exited", "make exited with wrong status") 195 assert(exit_code == 0, "make exited unsuccessfully") 196 return output 197end 198 199local function native_target() 200 local output = run_make({"MK_AUTO_OBJ=NO", "-V", "MACHINE", 201 "-V", "MACHINE_ARCH"}) 202 203 local arch, machine_arch 204 for x in output:gmatch("[^\n]+") do 205 if not arch then 206 arch = x 207 elseif not machine_arch then 208 machine_arch = x 209 end 210 end 211 212 return arch .. "/" .. machine_arch 213end 214 215local function src_targets() 216 local targets = {} 217 targets[native_target()] = true 218 219 local output = run_make({"MK_AUTO_OBJ=no", "targets"}) 220 local curline = 0 221 222 for line in output:gmatch("[^\n]+") do 223 curline = curline + 1 224 if curline ~= 1 then 225 local arch = line:match("[^%s]+/[^%s]+") 226 227 -- Make sure we don't roll over our default arch 228 if arch and not targets[arch] then 229 targets[arch] = false 230 end 231 end 232 end 233 234 return targets 235end 236 237local function config_options(srcconf, env, take_dupes, linting) 238 srcconf = srcconf or "/dev/null" 239 env = env or {} 240 241 local option_args = {".MAKE.MODE=normal", "showconfig", 242 "SRC_ENV_CONF=" .. srcconf} 243 244 for _, val in ipairs(env) do 245 option_args[#option_args + 1] = val 246 end 247 248 local output = run_make(option_args) 249 250 local options = {} 251 local known_dupes = {} 252 253 local function warn_on_dupe(option, val) 254 if not linting or known_dupes[option] then 255 return false 256 end 257 if not option:match("^OPT_") then 258 val = val == "yes" 259 end 260 261 known_dupes[option] = true 262 return val ~= options[val] 263 end 264 265 for opt in output:gmatch("[^\n]+") do 266 if opt:match("^MK_[%a%d_]+%s+=%s+.+") then 267 local name = opt:match("MK_[%a%d_]+") 268 local val = opt:match("= .+"):sub(3) 269 270 -- Some settings, e.g., MK_INIT_ALL_ZERO, may end up 271 -- output twice for some reason that I haven't dug into; 272 -- take the first value. In some circumstances, though, 273 -- we do make an exception and actually want to take the 274 -- latest. 275 if take_dupes or options[name] == nil then 276 options[name] = val == "yes" 277 elseif warn_on_dupe(name, val) then 278 io.stderr:write("ignoring duplicate option " .. 279 name .. "\n") 280 end 281 elseif opt:match("^OPT_[%a%d_]+%s+=%s+.+") then 282 local name = opt:match("OPT_[%a%d_]+") 283 local val = opt:match("= .+"):sub(3) 284 285 -- Multi-value options will arbitrarily use a table here 286 -- to indicate the difference. 287 if take_dupes or options[name] == nil then 288 options[name] = val 289 elseif warn_on_dupe(name, val) then 290 io.stderr:write("ignoring duplicate option " .. 291 name .. "\n") 292 end 293 end 294 end 295 296 return options 297end 298 299local function env_only_options() 300 local output = run_make({"MK_AUTO_OBJ=no", "-V", "__ENV_ONLY_OPTIONS"}) 301 local options = {} 302 303 for opt in output:gmatch("[^%s]+") do 304 options["MK_" .. opt] = true 305 end 306 307 return options 308end 309 310local function required_options() 311 local output = run_make({"-f", "share/mk/src.opts.mk", "-V", 312 "__REQUIRED_OPTIONS"}) 313 local options = {} 314 315 for opt in output:gmatch("[^%s]+") do 316 options["MK_" .. opt] = true 317 end 318 319 return options 320end 321 322local function config_description(option_name) 323 local fh = io.open(scriptdir .. "/" .. option_name) 324 local desc 325 326 if fh then 327 desc = "" 328 for line in fh:lines() do 329 if not line:match("%$FreeBSD%$") then 330 desc = desc .. line .. "\n" 331 end 332 end 333 334 assert(fh:close()) 335 end 336 337 return desc 338end 339 340local function config_descriptions(options) 341 local desc = {} 342 for name, _ in pairs(options) do 343 if name:match("^MK_") then 344 local basename = name:gsub("^MK_", "") 345 local with_name = "WITH_" .. basename 346 local without_name = "WITHOUT_" .. basename 347 348 desc[with_name] = config_description(with_name) 349 desc[without_name] = config_description(without_name) 350 elseif name:match("^OPT_") then 351 local basename = name:gsub("^OPT_", "") 352 353 desc[name] = config_description(basename) 354 end 355 end 356 return desc 357end 358 359local function dependent_options(tmpdir, option_name, all_opts, omit_others) 360 local opt_sense = not not option_name:match("^WITH_") 361 local base_option_name = option_name:gsub("^[^_]+_", "") 362 local prefix = (opt_sense and "WITHOUT_") or "WITH_" 363 364 local srcconf = tmpdir .. "/src-" ..prefix .. "ALL_" .. 365 option_name .. ".conf" 366 local fh = assert(io.open(srcconf, "w+")) 367 368 fh:write(option_name .. "=\"YES\"\n") 369 if not omit_others then 370 for opt, value in pairs(all_opts) do 371 local base_opt = opt:gsub("^MK_", "") 372 373 if base_opt ~= base_option_name then 374 local opt_prefix = (value and "WITH_") or "WITHOUT_" 375 fh:write(opt_prefix .. base_opt .. "=\"YES\"\n") 376 end 377 end 378 end 379 assert(fh:close()) 380 381 local option_name_key = "MK_" .. base_option_name 382 local options = config_options(srcconf, nil, omit_others) 383 for name, value in pairs(options) do 384 if name == option_name_key or value == all_opts[name] then 385 options[name] = nil 386 elseif name:match("^OPT_") then 387 -- Strip out multi-option values at the moment, they do 388 -- not really make sense. 389 options[name] = nil 390 end 391 end 392 393 return options 394end 395 396local function export_option_table(fd, name, options) 397 unistd.write(fd, name .. " = {") 398 for k, v in pairs(options) do 399 v = (v and "true") or "false" 400 unistd.write(fd, "['" .. k .. "'] = " .. v .. ",") 401 end 402 unistd.write(fd, "}") 403end 404 405local function all_dependent_options(tmpdir, options, default_opts, 406 with_all_opts, without_all_opts) 407 local all_enforced_options = {} 408 local all_effect_options = {} 409 local children = {} 410 411 for _, name in ipairs(options) do 412 local rfd, wfd = assert(unistd.pipe()) 413 local pid = assert(unistd.fork()) 414 415 if pid == 0 then 416 -- We need to pcall() this so that errors bubble up to 417 -- our _exit() call rather than the main exit. 418 local ret, errobj = pcall(function() 419 unistd.close(rfd) 420 421 local compare_table 422 if name:match("^WITHOUT") then 423 compare_table = with_all_opts 424 else 425 compare_table = without_all_opts 426 end 427 428 -- List of knobs forced on by this one 429 local enforced_options = dependent_options(tmpdir, name, 430 compare_table) 431 -- List of knobs implied by this by one (once additionally 432 -- filtered based on enforced_options values) 433 local effect_options = dependent_options(tmpdir, name, 434 default_opts, true) 435 436 export_option_table(wfd, "enforced_options", 437 enforced_options) 438 export_option_table(wfd, "effect_options", 439 effect_options) 440 end) 441 442 io.stderr:write(".") 443 444 if ret then 445 unistd._exit(0) 446 else 447 unistd.write(wfd, errobj) 448 unistd._exit(1) 449 end 450 end 451 452 unistd.close(wfd) 453 children[pid] = {name, rfd} 454 end 455 456 while next(children) ~= nil do 457::again:: 458 local pid, status, exitcode = sys_wait.wait(-1) 459 460 if status ~= "exited" then 461 goto again 462 end 463 464 local info = children[pid] 465 children[pid] = nil 466 467 local name = info[1] 468 local rfd = info[2] 469 local buf = '' 470 local rbuf, sz 471 472 -- Drain the pipe 473 rbuf = unistd.read(rfd, 512) 474 while #rbuf ~= 0 do 475 buf = buf .. rbuf 476 rbuf = unistd.read(rfd, 512) 477 end 478 479 unistd.close(rfd) 480 481 if exitcode ~= 0 then 482 error("Child " .. pid .. " failed, buf: " .. buf) 483 end 484 485 -- The child has written a pair of tables named enforced_options 486 -- and effect_options to the pipe. We'll load the pipe buffer 487 -- as a string and then yank these out of the clean environment 488 -- that we execute the chunk in. 489 local child_env = {} 490 local res, err = pcall(load(buf, "child", "t", child_env)) 491 492 all_enforced_options[name] = child_env["enforced_options"] 493 all_effect_options[name] = child_env["effect_options"] 494 end 495 496 io.stderr:write("\n") 497 return all_enforced_options, all_effect_options 498end 499 500local function get_defaults(target_archs, native_default_opts) 501 local target_defaults = {} 502 -- Set of options with differing defaults in some archs 503 local different_defaults = {} 504 505 for tgt, dflt in pairs(target_archs) do 506 if dflt then 507 local native_copy = {} 508 for opt, val in pairs(native_default_opts) do 509 native_copy[opt] = val 510 end 511 target_defaults[tgt] = native_copy 512 goto skip 513 end 514 515 local target = tgt:gsub("/.+$", "") 516 local target_arch = tgt:gsub("^.+/", "") 517 518 local target_opts = config_options(nil, {"TARGET=" .. target, 519 "TARGET_ARCH=" .. target_arch}) 520 521 for opt, val in pairs(target_opts) do 522 if val ~= native_default_opts[opt] then 523 different_defaults[opt] = true 524 end 525 end 526 527 target_defaults[tgt] = target_opts 528::skip:: 529 end 530 531 for opt in pairs(native_default_opts) do 532 if different_defaults[opt] == nil then 533 for _, opts in pairs(target_defaults) do 534 opts[opt] = nil 535 end 536 end 537 end 538 539 for tgt, opts in pairs(target_defaults) do 540 local val = opts["MK_ACPI"] 541 542 if val ~= nil then 543 print(" - " .. tgt .. ": " .. ((val and "yes") or "no")) 544 end 545 end 546 547 return target_defaults, different_defaults 548end 549 550local function option_comparator(lhs, rhs) 551 -- Convert both options to the base name, compare that instead unless 552 -- they're the same option. For the same option, we just want to get 553 -- ordering between WITH_/WITHOUT_ correct. 554 local base_lhs = lhs:gsub("^[^_]+_", "") 555 local base_rhs = rhs:gsub("^[^_]+_", "") 556 557 if base_lhs == base_rhs then 558 return lhs < rhs 559 else 560 return base_lhs < base_rhs 561 end 562end 563 564local function main(tmpdir) 565 io.stderr:write("building src.conf.5 man page from files in " .. 566 scriptdir .. "\n") 567 568 local env_only_opts <const> = env_only_options() 569 local default_opts = config_options(nil, nil, nil, true) 570 local opt_descriptions = config_descriptions(default_opts) 571 local srcconf_all <const> = tmpdir .. "/src-all-enabled.conf" 572 local fh = io.open(srcconf_all, "w+") 573 local all_targets = src_targets() 574 local target_defaults, different_defaults = get_defaults(all_targets, 575 default_opts) 576 local options = {} 577 local without_all_opts = {} 578 579 for name, value in pairs(default_opts) do 580 if name:match("^MK_") then 581 local base_name = name:gsub("^MK_", "") 582 local with_name = "WITH_" .. base_name 583 local without_name = "WITHOUT_" .. base_name 584 -- If it's differently defaulted on some architectures, 585 -- we'll split it into WITH_/WITHOUT_ just to simplify 586 -- some later bits. 587 if different_defaults[name] ~= nil then 588 options[#options + 1] = with_name 589 options[#options + 1] = without_name 590 elseif value then 591 options[#options + 1] = without_name 592 else 593 options[#options + 1] = with_name 594 end 595 596 without_all_opts[name] = false 597 assert(fh:write(with_name .. '="YES"\n')) 598 else 599 options[#options + 1] = name 600 end 601 end 602 603 assert(fh:close()) 604 605 local with_all_opts = config_options(srcconf_all) 606 local all_enforced_options, all_effect_options 607 local all_required_options = required_options() 608 609 all_enforced_options, all_effect_options = all_dependent_options(tmpdir, 610 options, default_opts, with_all_opts, without_all_opts) 611 612 table.sort(options, option_comparator) 613 io.stdout:write(output_head) 614 for _, name in ipairs(options) do 615 local value 616 617 if name:match("^OPT_") then 618 goto skip 619 end 620 assert(name:match("^WITH"), "Name looks wrong: " .. name) 621 local describe_option = name 622 623 value = not not name:match("^WITHOUT") 624 625 -- Normalize name to MK_ for indexing into various other 626 -- arrays 627 name = "MK_" .. name:gsub("^[^_]+_", "") 628 629 print(".It Va " .. describe_option:gsub("^OPT_", "")) 630 if opt_descriptions[describe_option] then 631 io.stdout:write(opt_descriptions[describe_option]) 632 else 633 io.stderr:write("Missing description for " .. 634 describe_option .. "\n") 635 end 636 637 local enforced_options = all_enforced_options[describe_option] 638 local effect_options = all_effect_options[describe_option] 639 640 if different_defaults[name] ~= nil then 641 print([[.Pp 642This is a default setting on]]) 643 644 local which_targets = {} 645 for tgt, tgt_options in pairs(target_defaults) do 646 if tgt_options[name] ~= value then 647 which_targets[#which_targets + 1] = tgt 648 end 649 end 650 651 table.sort(which_targets) 652 for idx, tgt in ipairs(which_targets) do 653 io.stdout:write(tgt) 654 if idx < #which_targets - 1 then 655 io.stdout:write(", ") 656 elseif idx == #which_targets - 1 then 657 io.stdout:write(" and ") 658 end 659 end 660 print(".") 661 end 662 663 -- Unset any implied options that are actually required. 664 for dep_opt in pairs(enforced_options) do 665 if all_required_options[dep_opt] then 666 enforced_options[dep_opt] = nil 667 end 668 end 669 if next(enforced_options) ~= nil then 670 print([[When set, it enforces these options: 671.Pp 672.Bl -item -compact]]) 673 674 local sorted_dep_opt = {} 675 for dep_opt in pairs(enforced_options) do 676 sorted_dep_opt[#sorted_dep_opt + 1] = dep_opt 677 end 678 679 table.sort(sorted_dep_opt) 680 for _, dep_opt in ipairs(sorted_dep_opt) do 681 local dep_val = enforced_options[dep_opt] 682 local dep_prefix = (dep_val and "WITH_") or 683 "WITHOUT_" 684 local dep_name = dep_opt:gsub("^MK_", 685 dep_prefix) 686 print(".It") 687 print(".Va " .. dep_name) 688 end 689 690 print(".El") 691 end 692 693 if next(effect_options) ~= nil then 694 if next(enforced_options) ~= nil then 695 -- Remove any options that were previously 696 -- noted as enforced... 697 for opt, val in pairs(effect_options) do 698 if enforced_options[opt] == val then 699 effect_options[opt] = nil 700 end 701 end 702 703 -- ... and this could leave us with an empty 704 -- set. 705 if next(effect_options) == nil then 706 goto noenforce 707 end 708 709 print(".Pp") 710 end 711 712 print([[When set, these options are also in effect: 713.Pp 714.Bl -inset -compact]]) 715 716 local sorted_dep_opt = {} 717 for dep_opt in pairs(effect_options) do 718 sorted_dep_opt[#sorted_dep_opt + 1] = dep_opt 719 end 720 721 table.sort(sorted_dep_opt) 722 for _, dep_opt in ipairs(sorted_dep_opt) do 723 local dep_val = effect_options[dep_opt] 724 local dep_prefix = (dep_val and "WITH_") or 725 "WITHOUT_" 726 local not_dep_prefix = ((not dep_val) and "WITH_") or 727 "WITHOUT_" 728 local dep_name = dep_opt:gsub("^MK_", 729 dep_prefix) 730 local not_dep_name = dep_opt:gsub("^MK_", 731 not_dep_prefix) 732 733 print(".It Va " .. dep_name) 734 print("(unless") 735 print(".Va " .. not_dep_name) 736 print("is set explicitly)") 737 end 738 739 print(".El") 740::noenforce:: 741 end 742 743 if env_only_opts[name] ~= nil then 744 print([[.Pp 745This must be set in the environment, make command line, or 746.Pa /etc/src-env.conf , 747not 748.Pa /etc/src.conf .]]) 749 end 750 ::skip:: 751 end 752 print([[.El 753.Pp 754The following options accept a single value from a list of valid values. 755.Bl -tag -width indent]]) 756 for _, name in ipairs(options) do 757 if name:match("^OPT_") then 758 local desc = opt_descriptions[name] 759 760 print(".It Va " .. name:gsub("^OPT_", "")) 761 if desc then 762 io.stdout:write(desc) 763 else 764 io.stderr:write("Missing description for " .. 765 name .. "\n") 766 end 767 end 768 end 769 io.stdout:write(output_tail) 770end 771 772local tmpdir = "/tmp/makeman." .. unistd.getpid() 773 774if not lfs.mkdir(tmpdir) then 775 error("Failed to create tempdir " .. tmpdir) 776end 777 778-- Catch any errors so that we can properly clean up, then re-throw it. 779local ret, errobj = pcall(main, tmpdir) 780 781for fname in lfs.dir(tmpdir) do 782 if fname ~= "." and fname ~= ".." then 783 assert(os.remove(tmpdir .. "/" .. fname)) 784 end 785end 786 787if not lfs.rmdir(tmpdir) then 788 assert(io.stderr:write("Failed to clean up tmpdir: " .. tmpdir .. "\n")) 789end 790 791if not ret then 792 io.stderr:write(errobj .. "\n") 793 os.exit(1) 794end 795