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 if exit_code ~= 0 then 196 io.stderr:write("Warning: make exited unsuccessfully\n" .. 197 "cmd: make " .. table.concat(args, " ") .. "\n") 198 end 199 return output 200end 201 202local function native_target() 203 local output = run_make({"MK_AUTO_OBJ=NO", "-V", "MACHINE", 204 "-V", "MACHINE_ARCH"}) 205 206 local arch, machine_arch 207 for x in output:gmatch("[^\n]+") do 208 if not arch then 209 arch = x 210 elseif not machine_arch then 211 machine_arch = x 212 end 213 end 214 215 return arch .. "/" .. machine_arch 216end 217 218local function src_targets() 219 local targets = {} 220 targets[native_target()] = true 221 222 local output = run_make({"MK_AUTO_OBJ=no", "targets"}) 223 local curline = 0 224 225 for line in output:gmatch("[^\n]+") do 226 curline = curline + 1 227 if curline ~= 1 then 228 local arch = line:match("[^%s]+/[^%s]+") 229 230 -- Make sure we don't roll over our default arch 231 if arch and not targets[arch] then 232 targets[arch] = false 233 end 234 end 235 end 236 237 return targets 238end 239 240local function config_options(srcconf, env, take_dupes, linting) 241 srcconf = srcconf or "/dev/null" 242 env = env or {} 243 244 local option_args = {".MAKE.MODE=normal", "showconfig", 245 "SRC_ENV_CONF=" .. srcconf} 246 247 for _, val in ipairs(env) do 248 option_args[#option_args + 1] = val 249 end 250 251 local output = run_make(option_args) 252 253 local options = {} 254 local known_dupes = {} 255 256 local function warn_on_dupe(option, val) 257 if not linting or known_dupes[option] then 258 return false 259 end 260 if not option:match("^OPT_") then 261 val = val == "yes" 262 end 263 264 known_dupes[option] = true 265 return val ~= options[val] 266 end 267 268 for opt in output:gmatch("[^\n]+") do 269 if opt:match("^MK_[%a%d_]+%s+=%s+.+") then 270 local name = opt:match("MK_[%a%d_]+") 271 local val = opt:match("= .+"):sub(3) 272 273 -- Some settings, e.g., MK_INIT_ALL_ZERO, may end up 274 -- output twice for some reason that I haven't dug into; 275 -- take the first value. In some circumstances, though, 276 -- we do make an exception and actually want to take the 277 -- latest. 278 if take_dupes or options[name] == nil then 279 options[name] = val == "yes" 280 elseif warn_on_dupe(name, val) then 281 io.stderr:write("ignoring duplicate option " .. 282 name .. "\n") 283 end 284 elseif opt:match("^OPT_[%a%d_]+%s+=%s+.+") then 285 local name = opt:match("OPT_[%a%d_]+") 286 local val = opt:match("= .+"):sub(3) 287 288 -- Multi-value options will arbitrarily use a table here 289 -- to indicate the difference. 290 if take_dupes or options[name] == nil then 291 options[name] = val 292 elseif warn_on_dupe(name, val) then 293 io.stderr:write("ignoring duplicate option " .. 294 name .. "\n") 295 end 296 end 297 end 298 299 return options 300end 301 302local function env_only_options() 303 local output = run_make({"MK_AUTO_OBJ=no", "-V", "__ENV_ONLY_OPTIONS"}) 304 local options = {} 305 306 for opt in output:gmatch("[^%s]+") do 307 options["MK_" .. opt] = true 308 end 309 310 return options 311end 312 313local function required_options() 314 local output = run_make({"-f", "share/mk/src.opts.mk", "-V", 315 "REQUIRED_OPTIONS"}) 316 local options = {} 317 318 for opt in output:gmatch("[^%s]+") do 319 options["MK_" .. opt] = true 320 end 321 322 return options 323end 324 325local function config_description(option_name) 326 local fh = io.open(scriptdir .. "/" .. option_name) 327 local desc 328 329 if fh then 330 desc = "" 331 for line in fh:lines() do 332 if not line:match("%$FreeBSD%$") then 333 desc = desc .. line .. "\n" 334 end 335 end 336 337 assert(fh:close()) 338 end 339 340 return desc 341end 342 343local function config_descriptions(options) 344 local desc = {} 345 for name, _ in pairs(options) do 346 if name:match("^MK_") then 347 local basename = name:gsub("^MK_", "") 348 local with_name = "WITH_" .. basename 349 local without_name = "WITHOUT_" .. basename 350 351 desc[with_name] = config_description(with_name) 352 desc[without_name] = config_description(without_name) 353 elseif name:match("^OPT_") then 354 local basename = name:gsub("^OPT_", "") 355 356 desc[name] = config_description(basename) 357 end 358 end 359 return desc 360end 361 362local function dependent_options(tmpdir, option_name, all_opts, omit_others) 363 local opt_sense = not not option_name:match("^WITH_") 364 local base_option_name = option_name:gsub("^[^_]+_", "") 365 local prefix = (opt_sense and "WITHOUT_") or "WITH_" 366 367 local srcconf = tmpdir .. "/src-" ..prefix .. "ALL_" .. 368 option_name .. ".conf" 369 local fh = assert(io.open(srcconf, "w+")) 370 371 fh:write(option_name .. "=\"YES\"\n") 372 if not omit_others then 373 for opt, value in pairs(all_opts) do 374 local base_opt = opt:gsub("^MK_", "") 375 376 if base_opt ~= base_option_name then 377 local opt_prefix = (value and "WITH_") or "WITHOUT_" 378 fh:write(opt_prefix .. base_opt .. "=\"YES\"\n") 379 end 380 end 381 end 382 assert(fh:close()) 383 384 local option_name_key = "MK_" .. base_option_name 385 local options = config_options(srcconf, nil, omit_others) 386 for name, value in pairs(options) do 387 if name == option_name_key or value == all_opts[name] then 388 options[name] = nil 389 elseif name:match("^OPT_") then 390 -- Strip out multi-option values at the moment, they do 391 -- not really make sense. 392 options[name] = nil 393 end 394 end 395 396 return options 397end 398 399local function export_option_table(fd, name, options) 400 unistd.write(fd, name .. " = {") 401 for k, v in pairs(options) do 402 v = (v and "true") or "false" 403 unistd.write(fd, "['" .. k .. "'] = " .. v .. ",") 404 end 405 unistd.write(fd, "}") 406end 407 408local function all_dependent_options(tmpdir, options, default_opts, 409 with_all_opts, without_all_opts) 410 local all_enforced_options = {} 411 local all_effect_options = {} 412 local children = {} 413 414 for _, name in ipairs(options) do 415 local rfd, wfd = assert(unistd.pipe()) 416 local pid = assert(unistd.fork()) 417 418 if pid == 0 then 419 -- We need to pcall() this so that errors bubble up to 420 -- our _exit() call rather than the main exit. 421 local ret, errobj = pcall(function() 422 unistd.close(rfd) 423 424 local compare_table 425 if name:match("^WITHOUT") then 426 compare_table = with_all_opts 427 else 428 compare_table = without_all_opts 429 end 430 431 -- List of knobs forced on by this one 432 local enforced_options = dependent_options(tmpdir, name, 433 compare_table) 434 -- List of knobs implied by this by one (once additionally 435 -- filtered based on enforced_options values) 436 local effect_options = dependent_options(tmpdir, name, 437 default_opts, true) 438 439 export_option_table(wfd, "enforced_options", 440 enforced_options) 441 export_option_table(wfd, "effect_options", 442 effect_options) 443 end) 444 445 io.stderr:write(".") 446 447 if ret then 448 unistd._exit(0) 449 else 450 unistd.write(wfd, errobj) 451 unistd._exit(1) 452 end 453 end 454 455 unistd.close(wfd) 456 children[pid] = {name, rfd} 457 end 458 459 while next(children) ~= nil do 460::again:: 461 local pid, status, exitcode = sys_wait.wait(-1) 462 463 if status ~= "exited" then 464 goto again 465 end 466 467 local info = children[pid] 468 children[pid] = nil 469 470 local name = info[1] 471 local rfd = info[2] 472 local buf = '' 473 local rbuf, sz 474 475 -- Drain the pipe 476 rbuf = unistd.read(rfd, 512) 477 while #rbuf ~= 0 do 478 buf = buf .. rbuf 479 rbuf = unistd.read(rfd, 512) 480 end 481 482 unistd.close(rfd) 483 484 if exitcode ~= 0 then 485 error("Child " .. pid .. " failed, buf: " .. buf) 486 end 487 488 -- The child has written a pair of tables named enforced_options 489 -- and effect_options to the pipe. We'll load the pipe buffer 490 -- as a string and then yank these out of the clean environment 491 -- that we execute the chunk in. 492 local child_env = {} 493 local res, err = pcall(load(buf, "child", "t", child_env)) 494 495 all_enforced_options[name] = child_env["enforced_options"] 496 all_effect_options[name] = child_env["effect_options"] 497 end 498 499 io.stderr:write("\n") 500 return all_enforced_options, all_effect_options 501end 502 503local function get_defaults(target_archs, native_default_opts) 504 local target_defaults = {} 505 -- Set of options with differing defaults in some archs 506 local different_defaults = {} 507 508 for tgt, dflt in pairs(target_archs) do 509 if dflt then 510 local native_copy = {} 511 for opt, val in pairs(native_default_opts) do 512 native_copy[opt] = val 513 end 514 target_defaults[tgt] = native_copy 515 goto skip 516 end 517 518 local target = tgt:gsub("/.+$", "") 519 local target_arch = tgt:gsub("^.+/", "") 520 521 local target_opts = config_options(nil, {"TARGET=" .. target, 522 "TARGET_ARCH=" .. target_arch}) 523 524 for opt, val in pairs(target_opts) do 525 if val ~= native_default_opts[opt] then 526 different_defaults[opt] = true 527 end 528 end 529 530 target_defaults[tgt] = target_opts 531::skip:: 532 end 533 534 for opt in pairs(native_default_opts) do 535 if different_defaults[opt] == nil then 536 for _, opts in pairs(target_defaults) do 537 opts[opt] = nil 538 end 539 end 540 end 541 542 for tgt, opts in pairs(target_defaults) do 543 local val = opts["MK_ACPI"] 544 545 if val ~= nil then 546 print(" - " .. tgt .. ": " .. ((val and "yes") or "no")) 547 end 548 end 549 550 return target_defaults, different_defaults 551end 552 553local function option_comparator(lhs, rhs) 554 -- Convert both options to the base name, compare that instead unless 555 -- they're the same option. For the same option, we just want to get 556 -- ordering between WITH_/WITHOUT_ correct. 557 local base_lhs = lhs:gsub("^[^_]+_", "") 558 local base_rhs = rhs:gsub("^[^_]+_", "") 559 560 if base_lhs == base_rhs then 561 return lhs < rhs 562 else 563 return base_lhs < base_rhs 564 end 565end 566 567local function main(tmpdir) 568 io.stderr:write("building src.conf.5 man page from files in " .. 569 scriptdir .. "\n") 570 571 local env_only_opts <const> = env_only_options() 572 local default_opts = config_options(nil, nil, nil, true) 573 local opt_descriptions = config_descriptions(default_opts) 574 local srcconf_all <const> = tmpdir .. "/src-all-enabled.conf" 575 local fh = io.open(srcconf_all, "w+") 576 local all_targets = src_targets() 577 local target_defaults, different_defaults = get_defaults(all_targets, 578 default_opts) 579 local options = {} 580 local without_all_opts = {} 581 582 for name, value in pairs(default_opts) do 583 if name:match("^MK_") then 584 local base_name = name:gsub("^MK_", "") 585 local with_name = "WITH_" .. base_name 586 local without_name = "WITHOUT_" .. base_name 587 -- If it's differently defaulted on some architectures, 588 -- we'll split it into WITH_/WITHOUT_ just to simplify 589 -- some later bits. 590 if different_defaults[name] ~= nil then 591 options[#options + 1] = with_name 592 options[#options + 1] = without_name 593 elseif value then 594 options[#options + 1] = without_name 595 else 596 options[#options + 1] = with_name 597 end 598 599 without_all_opts[name] = false 600 assert(fh:write(with_name .. '="YES"\n')) 601 else 602 options[#options + 1] = name 603 end 604 end 605 606 assert(fh:close()) 607 608 local with_all_opts = config_options(srcconf_all) 609 local all_enforced_options, all_effect_options 610 local all_required_options = required_options() 611 612 all_enforced_options, all_effect_options = all_dependent_options(tmpdir, 613 options, default_opts, with_all_opts, without_all_opts) 614 615 table.sort(options, option_comparator) 616 io.stdout:write(output_head) 617 for _, name in ipairs(options) do 618 local value 619 620 if name:match("^OPT_") then 621 goto skip 622 end 623 assert(name:match("^WITH"), "Name looks wrong: " .. name) 624 local describe_option = name 625 626 value = not not name:match("^WITHOUT") 627 628 -- Normalize name to MK_ for indexing into various other 629 -- arrays 630 name = "MK_" .. name:gsub("^[^_]+_", "") 631 632 print(".It Va " .. describe_option:gsub("^OPT_", "")) 633 if opt_descriptions[describe_option] then 634 io.stdout:write(opt_descriptions[describe_option]) 635 else 636 io.stderr:write("Missing description for " .. 637 describe_option .. "\n") 638 end 639 640 local enforced_options = all_enforced_options[describe_option] 641 local effect_options = all_effect_options[describe_option] 642 643 if different_defaults[name] ~= nil then 644 print([[.Pp 645This is a default setting on]]) 646 647 local which_targets = {} 648 for tgt, tgt_options in pairs(target_defaults) do 649 if tgt_options[name] ~= value then 650 which_targets[#which_targets + 1] = tgt 651 end 652 end 653 654 table.sort(which_targets) 655 for idx, tgt in ipairs(which_targets) do 656 io.stdout:write(tgt) 657 if idx < #which_targets - 1 then 658 io.stdout:write(", ") 659 elseif idx == #which_targets - 1 then 660 io.stdout:write(" and ") 661 end 662 end 663 print(".") 664 end 665 666 -- Unset any implied options that are actually required. 667 for dep_opt in pairs(enforced_options) do 668 if all_required_options[dep_opt] then 669 enforced_options[dep_opt] = nil 670 end 671 end 672 if next(enforced_options) ~= nil then 673 print([[When set, it enforces these options: 674.Pp 675.Bl -item -compact]]) 676 677 local sorted_dep_opt = {} 678 for dep_opt in pairs(enforced_options) do 679 sorted_dep_opt[#sorted_dep_opt + 1] = dep_opt 680 end 681 682 table.sort(sorted_dep_opt) 683 for _, dep_opt in ipairs(sorted_dep_opt) do 684 local dep_val = enforced_options[dep_opt] 685 local dep_prefix = (dep_val and "WITH_") or 686 "WITHOUT_" 687 local dep_name = dep_opt:gsub("^MK_", 688 dep_prefix) 689 print(".It") 690 print(".Va " .. dep_name) 691 end 692 693 print(".El") 694 end 695 696 if next(effect_options) ~= nil then 697 if next(enforced_options) ~= nil then 698 -- Remove any options that were previously 699 -- noted as enforced... 700 for opt, val in pairs(effect_options) do 701 if enforced_options[opt] == val then 702 effect_options[opt] = nil 703 end 704 end 705 706 -- ... and this could leave us with an empty 707 -- set. 708 if next(effect_options) == nil then 709 goto noenforce 710 end 711 712 print(".Pp") 713 end 714 715 print([[When set, these options are also in effect: 716.Pp 717.Bl -inset -compact]]) 718 719 local sorted_dep_opt = {} 720 for dep_opt in pairs(effect_options) do 721 sorted_dep_opt[#sorted_dep_opt + 1] = dep_opt 722 end 723 724 table.sort(sorted_dep_opt) 725 for _, dep_opt in ipairs(sorted_dep_opt) do 726 local dep_val = effect_options[dep_opt] 727 local dep_prefix = (dep_val and "WITH_") or 728 "WITHOUT_" 729 local not_dep_prefix = ((not dep_val) and "WITH_") or 730 "WITHOUT_" 731 local dep_name = dep_opt:gsub("^MK_", 732 dep_prefix) 733 local not_dep_name = dep_opt:gsub("^MK_", 734 not_dep_prefix) 735 736 print(".It Va " .. dep_name) 737 print("(unless") 738 print(".Va " .. not_dep_name) 739 print("is set explicitly)") 740 end 741 742 print(".El") 743::noenforce:: 744 end 745 746 if env_only_opts[name] ~= nil then 747 print([[.Pp 748This must be set in the environment, make command line, or 749.Pa /etc/src-env.conf , 750not 751.Pa /etc/src.conf .]]) 752 end 753 ::skip:: 754 end 755 print([[.El 756.Pp 757The following options accept a single value from a list of valid values. 758.Bl -tag -width indent]]) 759 for _, name in ipairs(options) do 760 if name:match("^OPT_") then 761 local desc = opt_descriptions[name] 762 763 print(".It Va " .. name:gsub("^OPT_", "")) 764 if desc then 765 io.stdout:write(desc) 766 else 767 io.stderr:write("Missing description for " .. 768 name .. "\n") 769 end 770 end 771 end 772 io.stdout:write(output_tail) 773end 774 775local tmpdir = "/tmp/makeman." .. unistd.getpid() 776 777if not lfs.mkdir(tmpdir) then 778 error("Failed to create tempdir " .. tmpdir) 779end 780 781-- Catch any errors so that we can properly clean up, then re-throw it. 782local ret, errobj = pcall(main, tmpdir) 783 784for fname in lfs.dir(tmpdir) do 785 if fname ~= "." and fname ~= ".." then 786 assert(os.remove(tmpdir .. "/" .. fname)) 787 end 788end 789 790if not lfs.rmdir(tmpdir) then 791 assert(io.stderr:write("Failed to clean up tmpdir: " .. tmpdir .. "\n")) 792end 793 794if not ret then 795 io.stderr:write(errobj .. "\n") 796 os.exit(1) 797end 798