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