aboutsummaryrefslogtreecommitdiff
path: root/tools/build/options/makeman.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tools/build/options/makeman.lua')
-rw-r--r--tools/build/options/makeman.lua791
1 files changed, 791 insertions, 0 deletions
diff --git a/tools/build/options/makeman.lua b/tools/build/options/makeman.lua
new file mode 100644
index 000000000000..e96e6f50174b
--- /dev/null
+++ b/tools/build/options/makeman.lua
@@ -0,0 +1,791 @@
+--
+-- Copyright (c) 2023 Kyle Evans <kevans@FreeBSD.org>
+--
+-- SPDX-License-Identifier: BSD-2-Clause
+--
+
+local libgen = require('posix.libgen')
+local lfs = require('lfs')
+local stdlib = require('posix.stdlib')
+local unistd = require('posix.unistd')
+local sys_wait = require('posix.sys.wait')
+
+local curdate = os.date("%B %e, %Y")
+
+local output_head <const> = ".\\\" DO NOT EDIT-- this file is @" .. [[generated by tools/build/options/makeman.
+.Dd ]] .. curdate .. [[
+
+.Dt SRC.CONF 5
+.Os
+.Sh NAME
+.Nm src.conf
+.Nd "source build options"
+.Sh DESCRIPTION
+The
+.Nm
+file contains variables that control what components will be generated during
+the build process of the
+.Fx
+source tree; see
+.Xr build 7 .
+.Pp
+The
+.Nm
+file uses the standard makefile syntax.
+However,
+.Nm
+should not specify any dependencies to
+.Xr make 1 .
+Instead,
+.Nm
+is to set
+.Xr make 1
+variables that control the aspects of how the system builds.
+.Pp
+The default location of
+.Nm
+is
+.Pa /etc/src.conf ,
+though an alternative location can be specified in the
+.Xr make 1
+variable
+.Va SRCCONF .
+Overriding the location of
+.Nm
+may be necessary if the system-wide settings are not suitable
+for a particular build.
+For instance, setting
+.Va SRCCONF
+to
+.Pa /dev/null
+effectively resets all build controls to their defaults.
+.Pp
+The only purpose of
+.Nm
+is to control the compilation of the
+.Fx
+source code, which is usually located in
+.Pa /usr/src .
+As a rule, the system administrator creates
+.Nm
+when the values of certain control variables need to be changed
+from their defaults.
+.Pp
+In addition, control variables can be specified
+for a particular build via the
+.Fl D
+option of
+.Xr make 1
+or in its environment; see
+.Xr environ 7 .
+.Pp
+The environment of
+.Xr make 1
+for the build can be controlled via the
+.Va SRC_ENV_CONF
+variable, which defaults to
+.Pa /etc/src-env.conf .
+Some examples that may only be set in this file are
+.Va WITH_DIRDEPS_BUILD ,
+and
+.Va WITH_META_MODE ,
+and
+.Va MAKEOBJDIRPREFIX
+as they are environment-only variables.
+.Pp
+The values of
+.Va WITH_
+and
+.Va WITHOUT_
+variables are ignored regardless of their setting;
+even if they would be set to
+.Dq Li FALSE
+or
+.Dq Li NO .
+The presence of an option causes
+it to be honored by
+.Xr make 1 .
+.Pp
+This list provides a name and short description for variables
+that can be used for source builds.
+.Bl -tag -width indent
+]]
+
+local output_tail <const> = [[.El
+.Sh FILES
+.Bl -tag -compact -width Pa
+.It Pa /etc/src.conf
+.It Pa /etc/src-env.conf
+.It Pa /usr/share/mk/bsd.own.mk
+.El
+.Sh SEE ALSO
+.Xr make 1 ,
+.Xr make.conf 5 ,
+.Xr build 7 ,
+.Xr ports 7
+.Sh HISTORY
+The
+.Nm
+file appeared in
+.Fx 7.0 .
+.Sh AUTHORS
+This manual page was autogenerated by
+.An tools/build/options/makeman .
+]]
+
+local scriptdir <const> = libgen.dirname(stdlib.realpath(arg[0]))
+local srcdir <const> = stdlib.realpath(scriptdir .. "/../../../")
+local makesysdir <const> = srcdir .. "/share/mk"
+
+local make_envvar = os.getenv("MAKE")
+local make_cmd_override = {}
+if make_envvar then
+ for word in make_envvar:gmatch("[^%s]+") do
+ make_cmd_override[#make_cmd_override + 1] = word
+ end
+end
+
+-- Lifted from bsdinstall/scripts/pkgbase.in (read_all)
+local function read_pipe(pipe)
+ local ret = ""
+ repeat
+ local buffer = assert(unistd.read(pipe, 1024))
+ ret = ret .. buffer
+ until buffer == ""
+ return ret
+end
+local function run_make(args)
+ local cmd_args = {"env", "-i", "make", "-C", srcdir, "-m", makesysdir,
+ "__MAKE_CONF=/dev/null", "SRCCONF=/dev/null"}
+
+ if #make_cmd_override > 0 then
+ cmd_args[3] = make_cmd_override[1]
+ for k = 2, #make_cmd_override do
+ local val = make_cmd_override[k]
+
+ table.insert(cmd_args, 3 + (k - 1), val)
+ end
+ end
+ for k, v in ipairs(args) do
+ cmd_args[#cmd_args + 1] = v
+ end
+
+ local r, w = assert(unistd.pipe())
+ local pid = assert(unistd.fork())
+ if pid == 0 then
+ -- Child
+ assert(unistd.close(r))
+ assert(unistd.dup2(w, 1))
+ assert(unistd.dup2(w, 2))
+ assert(unistd.execp("env", cmd_args))
+ unistd._exit()
+ end
+
+ -- Parent
+ assert(unistd.close(w))
+
+ local output = read_pipe(r)
+ assert(unistd.close(r))
+
+ local _, exit_type, exit_code = assert(sys_wait.wait(pid))
+ assert(exit_type == "exited", "make exited with wrong status")
+ assert(exit_code == 0, "make exited unsuccessfully")
+ return output
+end
+
+local function native_target()
+ local output = run_make({"MK_AUTO_OBJ=NO", "-V", "MACHINE",
+ "-V", "MACHINE_ARCH"})
+
+ local arch, machine_arch
+ for x in output:gmatch("[^\n]+") do
+ if not arch then
+ arch = x
+ elseif not machine_arch then
+ machine_arch = x
+ end
+ end
+
+ return arch .. "/" .. machine_arch
+end
+
+local function src_targets()
+ local targets = {}
+ targets[native_target()] = true
+
+ local output = run_make({"MK_AUTO_OBJ=no", "targets"})
+ local curline = 0
+
+ for line in output:gmatch("[^\n]+") do
+ curline = curline + 1
+ if curline ~= 1 then
+ local arch = line:match("[^%s]+/[^%s]+")
+
+ -- Make sure we don't roll over our default arch
+ if arch and not targets[arch] then
+ targets[arch] = false
+ end
+ end
+ end
+
+ return targets
+end
+
+local function config_options(srcconf, env, take_dupes, linting)
+ srcconf = srcconf or "/dev/null"
+ env = env or {}
+
+ local option_args = {".MAKE.MODE=normal", "showconfig",
+ "SRC_ENV_CONF=" .. srcconf}
+
+ for _, val in ipairs(env) do
+ option_args[#option_args + 1] = val
+ end
+
+ local output = run_make(option_args)
+
+ local options = {}
+ local known_dupes = {}
+
+ local function warn_on_dupe(option, val)
+ if not linting or known_dupes[option] then
+ return false
+ end
+ if not option:match("^OPT_") then
+ val = val == "yes"
+ end
+
+ known_dupes[option] = true
+ return val ~= options[val]
+ end
+
+ for opt in output:gmatch("[^\n]+") do
+ if opt:match("^MK_[%a%d_]+%s+=%s+.+") then
+ local name = opt:match("MK_[%a%d_]+")
+ local val = opt:match("= .+"):sub(3)
+
+ -- Some settings, e.g., MK_INIT_ALL_ZERO, may end up
+ -- output twice for some reason that I haven't dug into;
+ -- take the first value. In some circumstances, though,
+ -- we do make an exception and actually want to take the
+ -- latest.
+ if take_dupes or options[name] == nil then
+ options[name] = val == "yes"
+ elseif warn_on_dupe(name, val) then
+ io.stderr:write("ignoring duplicate option " ..
+ name .. "\n")
+ end
+ elseif opt:match("^OPT_[%a%d_]+%s+=%s+.+") then
+ local name = opt:match("OPT_[%a%d_]+")
+ local val = opt:match("= .+"):sub(3)
+
+ -- Multi-value options will arbitrarily use a table here
+ -- to indicate the difference.
+ if take_dupes or options[name] == nil then
+ options[name] = val
+ elseif warn_on_dupe(name, val) then
+ io.stderr:write("ignoring duplicate option " ..
+ name .. "\n")
+ end
+ end
+ end
+
+ return options
+end
+
+local function env_only_options()
+ local output = run_make({"MK_AUTO_OBJ=no", "-V", "__ENV_ONLY_OPTIONS"})
+ local options = {}
+
+ for opt in output:gmatch("[^%s]+") do
+ options["MK_" .. opt] = true
+ end
+
+ return options
+end
+
+local function required_options()
+ local output = run_make({"-f", "share/mk/src.opts.mk", "-V",
+ "__REQUIRED_OPTIONS"})
+ local options = {}
+
+ for opt in output:gmatch("[^%s]+") do
+ options["MK_" .. opt] = true
+ end
+
+ return options
+end
+
+local function config_description(option_name)
+ local fh = io.open(scriptdir .. "/" .. option_name)
+ local desc
+
+ if fh then
+ desc = ""
+ for line in fh:lines() do
+ if not line:match("%$FreeBSD%$") then
+ desc = desc .. line .. "\n"
+ end
+ end
+
+ assert(fh:close())
+ end
+
+ return desc
+end
+
+local function config_descriptions(options)
+ local desc = {}
+ for name, _ in pairs(options) do
+ if name:match("^MK_") then
+ local basename = name:gsub("^MK_", "")
+ local with_name = "WITH_" .. basename
+ local without_name = "WITHOUT_" .. basename
+
+ desc[with_name] = config_description(with_name)
+ desc[without_name] = config_description(without_name)
+ elseif name:match("^OPT_") then
+ local basename = name:gsub("^OPT_", "")
+
+ desc[name] = config_description(basename)
+ end
+ end
+ return desc
+end
+
+local function dependent_options(tmpdir, option_name, all_opts, omit_others)
+ local opt_sense = not not option_name:match("^WITH_")
+ local base_option_name = option_name:gsub("^[^_]+_", "")
+ local prefix = (opt_sense and "WITHOUT_") or "WITH_"
+
+ local srcconf = tmpdir .. "/src-" ..prefix .. "ALL_" ..
+ option_name .. ".conf"
+ local fh = assert(io.open(srcconf, "w+"))
+
+ fh:write(option_name .. "=\"YES\"\n")
+ if not omit_others then
+ for opt, value in pairs(all_opts) do
+ local base_opt = opt:gsub("^MK_", "")
+
+ if base_opt ~= base_option_name then
+ local opt_prefix = (value and "WITH_") or "WITHOUT_"
+ fh:write(opt_prefix .. base_opt .. "=\"YES\"\n")
+ end
+ end
+ end
+ assert(fh:close())
+
+ local option_name_key = "MK_" .. base_option_name
+ local options = config_options(srcconf, nil, omit_others)
+ for name, value in pairs(options) do
+ if name == option_name_key or value == all_opts[name] then
+ options[name] = nil
+ elseif name:match("^OPT_") then
+ -- Strip out multi-option values at the moment, they do
+ -- not really make sense.
+ options[name] = nil
+ end
+ end
+
+ return options
+end
+
+local function export_option_table(fd, name, options)
+ unistd.write(fd, name .. " = {")
+ for k, v in pairs(options) do
+ v = (v and "true") or "false"
+ unistd.write(fd, "['" .. k .. "'] = " .. v .. ",")
+ end
+ unistd.write(fd, "}")
+end
+
+local function all_dependent_options(tmpdir, options, default_opts,
+ with_all_opts, without_all_opts)
+ local all_enforced_options = {}
+ local all_effect_options = {}
+ local children = {}
+
+ for _, name in ipairs(options) do
+ local rfd, wfd = assert(unistd.pipe())
+ local pid = assert(unistd.fork())
+
+ if pid == 0 then
+ -- We need to pcall() this so that errors bubble up to
+ -- our _exit() call rather than the main exit.
+ local ret, errobj = pcall(function()
+ unistd.close(rfd)
+
+ local compare_table
+ if name:match("^WITHOUT") then
+ compare_table = with_all_opts
+ else
+ compare_table = without_all_opts
+ end
+
+ -- List of knobs forced on by this one
+ local enforced_options = dependent_options(tmpdir, name,
+ compare_table)
+ -- List of knobs implied by this by one (once additionally
+ -- filtered based on enforced_options values)
+ local effect_options = dependent_options(tmpdir, name,
+ default_opts, true)
+
+ export_option_table(wfd, "enforced_options",
+ enforced_options)
+ export_option_table(wfd, "effect_options",
+ effect_options)
+ end)
+
+ io.stderr:write(".")
+
+ if ret then
+ unistd._exit(0)
+ else
+ unistd.write(wfd, errobj)
+ unistd._exit(1)
+ end
+ end
+
+ unistd.close(wfd)
+ children[pid] = {name, rfd}
+ end
+
+ while next(children) ~= nil do
+::again::
+ local pid, status, exitcode = sys_wait.wait(-1)
+
+ if status ~= "exited" then
+ goto again
+ end
+
+ local info = children[pid]
+ children[pid] = nil
+
+ local name = info[1]
+ local rfd = info[2]
+ local buf = ''
+ local rbuf, sz
+
+ -- Drain the pipe
+ rbuf = unistd.read(rfd, 512)
+ while #rbuf ~= 0 do
+ buf = buf .. rbuf
+ rbuf = unistd.read(rfd, 512)
+ end
+
+ unistd.close(rfd)
+
+ if exitcode ~= 0 then
+ error("Child " .. pid .. " failed, buf: " .. buf)
+ end
+
+ -- The child has written a pair of tables named enforced_options
+ -- and effect_options to the pipe. We'll load the pipe buffer
+ -- as a string and then yank these out of the clean environment
+ -- that we execute the chunk in.
+ local child_env = {}
+ local res, err = pcall(load(buf, "child", "t", child_env))
+
+ all_enforced_options[name] = child_env["enforced_options"]
+ all_effect_options[name] = child_env["effect_options"]
+ end
+
+ io.stderr:write("\n")
+ return all_enforced_options, all_effect_options
+end
+
+local function get_defaults(target_archs, native_default_opts)
+ local target_defaults = {}
+ -- Set of options with differing defaults in some archs
+ local different_defaults = {}
+
+ for tgt, dflt in pairs(target_archs) do
+ if dflt then
+ local native_copy = {}
+ for opt, val in pairs(native_default_opts) do
+ native_copy[opt] = val
+ end
+ target_defaults[tgt] = native_copy
+ goto skip
+ end
+
+ local target = tgt:gsub("/.+$", "")
+ local target_arch = tgt:gsub("^.+/", "")
+
+ local target_opts = config_options(nil, {"TARGET=" .. target,
+ "TARGET_ARCH=" .. target_arch})
+
+ for opt, val in pairs(target_opts) do
+ if val ~= native_default_opts[opt] then
+ different_defaults[opt] = true
+ end
+ end
+
+ target_defaults[tgt] = target_opts
+::skip::
+ end
+
+ for opt in pairs(native_default_opts) do
+ if different_defaults[opt] == nil then
+ for _, opts in pairs(target_defaults) do
+ opts[opt] = nil
+ end
+ end
+ end
+
+ for tgt, opts in pairs(target_defaults) do
+ local val = opts["MK_ACPI"]
+
+ if val ~= nil then
+ print(" - " .. tgt .. ": " .. ((val and "yes") or "no"))
+ end
+ end
+
+ return target_defaults, different_defaults
+end
+
+local function option_comparator(lhs, rhs)
+ -- Convert both options to the base name, compare that instead unless
+ -- they're the same option. For the same option, we just want to get
+ -- ordering between WITH_/WITHOUT_ correct.
+ local base_lhs = lhs:gsub("^[^_]+_", "")
+ local base_rhs = rhs:gsub("^[^_]+_", "")
+
+ if base_lhs == base_rhs then
+ return lhs < rhs
+ else
+ return base_lhs < base_rhs
+ end
+end
+
+local function main(tmpdir)
+ io.stderr:write("building src.conf.5 man page from files in " ..
+ scriptdir .. "\n")
+
+ local env_only_opts <const> = env_only_options()
+ local default_opts = config_options(nil, nil, nil, true)
+ local opt_descriptions = config_descriptions(default_opts)
+ local srcconf_all <const> = tmpdir .. "/src-all-enabled.conf"
+ local fh = io.open(srcconf_all, "w+")
+ local all_targets = src_targets()
+ local target_defaults, different_defaults = get_defaults(all_targets,
+ default_opts)
+ local options = {}
+ local without_all_opts = {}
+
+ for name, value in pairs(default_opts) do
+ if name:match("^MK_") then
+ local base_name = name:gsub("^MK_", "")
+ local with_name = "WITH_" .. base_name
+ local without_name = "WITHOUT_" .. base_name
+ -- If it's differently defaulted on some architectures,
+ -- we'll split it into WITH_/WITHOUT_ just to simplify
+ -- some later bits.
+ if different_defaults[name] ~= nil then
+ options[#options + 1] = with_name
+ options[#options + 1] = without_name
+ elseif value then
+ options[#options + 1] = without_name
+ else
+ options[#options + 1] = with_name
+ end
+
+ without_all_opts[name] = false
+ assert(fh:write(with_name .. '="YES"\n'))
+ else
+ options[#options + 1] = name
+ end
+ end
+
+ assert(fh:close())
+
+ local with_all_opts = config_options(srcconf_all)
+ local all_enforced_options, all_effect_options
+ local all_required_options = required_options()
+
+ all_enforced_options, all_effect_options = all_dependent_options(tmpdir,
+ options, default_opts, with_all_opts, without_all_opts)
+
+ table.sort(options, option_comparator)
+ io.stdout:write(output_head)
+ for _, name in ipairs(options) do
+ local value
+
+ if name:match("^OPT_") then
+ goto skip
+ end
+ assert(name:match("^WITH"), "Name looks wrong: " .. name)
+ local describe_option = name
+
+ value = not not name:match("^WITHOUT")
+
+ -- Normalize name to MK_ for indexing into various other
+ -- arrays
+ name = "MK_" .. name:gsub("^[^_]+_", "")
+
+ print(".It Va " .. describe_option:gsub("^OPT_", ""))
+ if opt_descriptions[describe_option] then
+ io.stdout:write(opt_descriptions[describe_option])
+ else
+ io.stderr:write("Missing description for " ..
+ describe_option .. "\n")
+ end
+
+ local enforced_options = all_enforced_options[describe_option]
+ local effect_options = all_effect_options[describe_option]
+
+ if different_defaults[name] ~= nil then
+ print([[.Pp
+This is a default setting on]])
+
+ local which_targets = {}
+ for tgt, tgt_options in pairs(target_defaults) do
+ if tgt_options[name] ~= value then
+ which_targets[#which_targets + 1] = tgt
+ end
+ end
+
+ table.sort(which_targets)
+ for idx, tgt in ipairs(which_targets) do
+ io.stdout:write(tgt)
+ if idx < #which_targets - 1 then
+ io.stdout:write(", ")
+ elseif idx == #which_targets - 1 then
+ io.stdout:write(" and ")
+ end
+ end
+ print(".")
+ end
+
+ -- Unset any implied options that are actually required.
+ for dep_opt in pairs(enforced_options) do
+ if all_required_options[dep_opt] then
+ enforced_options[dep_opt] = nil
+ end
+ end
+ if next(enforced_options) ~= nil then
+ print([[When set, it enforces these options:
+.Pp
+.Bl -item -compact]])
+
+ local sorted_dep_opt = {}
+ for dep_opt in pairs(enforced_options) do
+ sorted_dep_opt[#sorted_dep_opt + 1] = dep_opt
+ end
+
+ table.sort(sorted_dep_opt)
+ for _, dep_opt in ipairs(sorted_dep_opt) do
+ local dep_val = enforced_options[dep_opt]
+ local dep_prefix = (dep_val and "WITH_") or
+ "WITHOUT_"
+ local dep_name = dep_opt:gsub("^MK_",
+ dep_prefix)
+ print(".It")
+ print(".Va " .. dep_name)
+ end
+
+ print(".El")
+ end
+
+ if next(effect_options) ~= nil then
+ if next(enforced_options) ~= nil then
+ -- Remove any options that were previously
+ -- noted as enforced...
+ for opt, val in pairs(effect_options) do
+ if enforced_options[opt] == val then
+ effect_options[opt] = nil
+ end
+ end
+
+ -- ... and this could leave us with an empty
+ -- set.
+ if next(effect_options) == nil then
+ goto noenforce
+ end
+
+ print(".Pp")
+ end
+
+ print([[When set, these options are also in effect:
+.Pp
+.Bl -inset -compact]])
+
+ local sorted_dep_opt = {}
+ for dep_opt in pairs(effect_options) do
+ sorted_dep_opt[#sorted_dep_opt + 1] = dep_opt
+ end
+
+ table.sort(sorted_dep_opt)
+ for _, dep_opt in ipairs(sorted_dep_opt) do
+ local dep_val = effect_options[dep_opt]
+ local dep_prefix = (dep_val and "WITH_") or
+ "WITHOUT_"
+ local not_dep_prefix = ((not dep_val) and "WITH_") or
+ "WITHOUT_"
+ local dep_name = dep_opt:gsub("^MK_",
+ dep_prefix)
+ local not_dep_name = dep_opt:gsub("^MK_",
+ not_dep_prefix)
+
+ print(".It Va " .. dep_name)
+ print("(unless")
+ print(".Va " .. not_dep_name)
+ print("is set explicitly)")
+ end
+
+ print(".El")
+::noenforce::
+ end
+
+ if env_only_opts[name] ~= nil then
+ print([[.Pp
+This must be set in the environment, make command line, or
+.Pa /etc/src-env.conf ,
+not
+.Pa /etc/src.conf .]])
+ end
+ ::skip::
+ end
+ print([[.El
+.Pp
+The following options accept a single value from a list of valid values.
+.Bl -tag -width indent]])
+ for _, name in ipairs(options) do
+ if name:match("^OPT_") then
+ local desc = opt_descriptions[name]
+
+ print(".It Va " .. name:gsub("^OPT_", ""))
+ if desc then
+ io.stdout:write(desc)
+ else
+ io.stderr:write("Missing description for " ..
+ name .. "\n")
+ end
+ end
+ end
+ io.stdout:write(output_tail)
+end
+
+local tmpdir = "/tmp/makeman." .. unistd.getpid()
+
+if not lfs.mkdir(tmpdir) then
+ error("Failed to create tempdir " .. tmpdir)
+end
+
+-- Catch any errors so that we can properly clean up, then re-throw it.
+local ret, errobj = pcall(main, tmpdir)
+
+for fname in lfs.dir(tmpdir) do
+ if fname ~= "." and fname ~= ".." then
+ assert(os.remove(tmpdir .. "/" .. fname))
+ end
+end
+
+if not lfs.rmdir(tmpdir) then
+ assert(io.stderr:write("Failed to clean up tmpdir: " .. tmpdir .. "\n"))
+end
+
+if not ret then
+ io.stderr:write(errobj .. "\n")
+ os.exit(1)
+end