xref: /freebsd/release/packages/generate-ucl.lua (revision a6ac2c98c9cb22a4a2efc12cafce2d2ba82b3fdc)
1#!/usr/libexec/flua
2--
3-- Copyright (c) 2024-2025 Baptiste Daroussin <bapt@FreeBSD.org>
4-- Copyright (c) 2025 Lexi Winter <ivy@FreeBSD.org>
5--
6-- SPDX-License-Identifier: BSD-2-Clause
7--
8
9--[[ usage:
10generate-ucl.lua [<variablename> <variablevalue>]... <sourceucl> <destucl>
11
12Build a package's UCL configuration by loading the template UCL file
13<sourceucl>, replacing any $VARIABLES in the UCL based on the provided
14variables, then writing the result to <destucl>.
15]]--
16
17local ucl = require("ucl")
18
19-- Give subpackages a special comment and description suffix to indicate what
20-- they contain, so e.g. "foo-man" has " (manual pages)" appended to its
21-- comment.  This avoids having to create a separate ucl files for every
22-- subpackage just to set this.
23--
24-- Note that this is not a key table because the order of the pattern matches
25-- is important.
26pkg_suffixes = {
27	{
28		"%-dev%-lib32$", "(32-bit development files)",
29		"This package contains development files for compiling "..
30		"32-bit applications on a 64-bit host."
31	},
32	{
33		"%-dbg%-lib32$", "(32-bit debugging symbols)",
34		"This package contains 32-bit external debugging symbols "..
35		"for use with a source-level debugger.",
36	},
37	{
38		"%-man%-lib32$", "(32-bit manual pages)",
39		"This package contains the online manual pages for 32-bit "..
40		"components on a 64-bit host.",
41	},
42	{
43		"%-lib32$", "(32-bit libraries)",
44		"This package contains 32-bit libraries for running 32-bit "..
45		"applications on a 64-bit host.",
46	},
47	{
48		"%-lib$", "(libraries)",
49		"This package contains runtime shared libraries.",
50	},
51	{
52		"%-dev$", "(development files)",
53		"This package contains development files for "..
54		"compiling applications."
55	},
56	{
57		"%-man$", "(manual pages)",
58		"This package contains the online manual pages."
59	},
60	{
61		"%-dbg$", "(debugging symbols)",
62		"This package contains external debugging symbols for use "..
63		"with a source-level debugger.",
64	},
65}
66
67-- A list of packages which don't get the automatic suffix handling,
68-- e.g. -man packages with no corresponding base package.
69local no_suffix_pkgs = {
70	["kernel-man"] = true,
71}
72
73function add_suffixes(obj)
74	local pkgname = obj["name"]
75
76	for _,pattern in pairs(pkg_suffixes) do
77		if pkgname:match(pattern[1]) ~= nil then
78			obj["comment"] = obj["comment"] .. " " .. pattern[2]
79			obj["desc"] = obj["desc"] .. "\n\n" .. pattern[3]
80			return
81		end
82	end
83end
84
85-- Hardcode a list of packages which don't get the automatic pkggenname
86-- dependency because the base package doesn't exist.  We should have a better
87-- way to handle this.
88local no_gen_deps = {
89	["libcompat-dev"] = true,
90	["libcompat-dev-lib32"] = true,
91	["libcompat-man"] = true,
92	["libcompiler_rt-dev"] = true,
93	["libcompiler_rt-dev-lib32"] = true,
94	["liby-dev"] = true,
95	["liby-dev-lib32"] = true,
96	["kernel-man"] = true,
97}
98
99-- Return true if the package 'pkgname' should have a dependency on the package
100-- pkggenname.
101function add_gen_dep(pkgname, pkggenname)
102	if pkgname == pkggenname then
103		return false
104	end
105	if pkgname == nil or pkggenname == nil then
106		return false
107	end
108	if no_gen_deps[pkgname] ~= nil then
109		return false
110	end
111	if pkgname:match("%-lib$") ~= nil then
112		return false
113	end
114	if pkggenname == "kernel" then
115		return false
116	end
117
118	return true
119end
120
121local pkgname = nil
122local pkggenname = nil
123local pkgprefix = nil
124local pkgversion = nil
125
126-- This parser is the output UCL we want to build.
127local parser = ucl.parser()
128
129-- Set any $VARIABLES from the command line in the parser.  This causes ucl to
130-- automatically replace them when we load the source ucl.
131if #arg < 2 or #arg % 2 ~= 0 then
132	io.stderr:write(arg[0] .. ": expected an even number of arguments, got " .. #arg)
133	os.exit(1)
134end
135
136for i = 2, #arg - 2, 2 do
137	local varname = arg[i - 1]
138	local varvalue = arg[i]
139
140	if varname == "PKGNAME" and #varvalue > 0 then
141		pkgname = varvalue
142	elseif varname == "PKGGENNAME" and #varvalue > 0 then
143		pkggenname = varvalue
144	elseif varname == "VERSION" and #varvalue > 0 then
145		pkgversion = varvalue
146	elseif varname == "PKG_NAME_PREFIX" and #varvalue > 0 then
147		pkgprefix = varvalue
148	end
149
150	parser:register_variable(varname, varvalue)
151end
152
153-- Load the source ucl file.
154local res,err = parser:parse_file(arg[#arg - 1])
155if not res then
156	io.stderr:write(arg[0] .. ": fail to parse("..arg[#arg - 1].."): "..err)
157	os.exit(1)
158end
159
160local obj = parser:get_object()
161
162-- If pkgname is different from pkggenname, add a dependency on pkggenname.
163-- This means that e.g. -dev packages depend on their respective base package.
164if add_gen_dep(pkgname, pkggenname) then
165	if obj["deps"] == nil then
166		obj["deps"] = {}
167	end
168	obj["deps"][pkggenname] = {
169		["version"] = pkgversion,
170		["origin"] = "base/"..pkgprefix.."-"..pkggenname,
171	}
172end
173
174--
175-- Handle the 'set' annotation, a comma-separated list of sets which this
176-- package should be placed in.  If it's not specified, the package goes
177-- in the default set which is base.
178--
179-- Ensure we have an annotations table to work with.
180obj["annotations"] = obj["annotations"] or {}
181-- If no set is provided, use the default set which is "base".
182sets = obj["annotations"]["set"] or "base"
183-- For subpackages, we may need to rewrite the set name.  This is done a little
184-- differently from the normal pkg suffix processing, because we don't need sets
185-- to be as a granular as the base packages.
186--
187-- Create a single lib32 set for all lib32 packages.  Most users don't need
188-- lib32, so this avoids creating a large number of unnecessary lib32 sets.
189-- However, lib32 debug symbols still go into their own package since they're
190-- quite large.
191if pkgname:match("%-dbg%-lib32$") then
192	sets = "lib32-dbg"
193elseif pkgname:match("%-lib32$") then
194	sets = "lib32"
195-- If this is a -dev package, put it in a single set called "devel" which
196-- contains all development files.  Also include lib*-man packages, which
197-- contain manpages for libraries. Having a separate <set>-dev for every
198-- set is not necessary, because generally you either want development
199-- support or you don't.
200elseif pkgname:match("%-dev$") or pkgname:match("^lib.*%-man$") then
201	sets = "devel"
202-- Don't separate tests and tests-dbg into 2 sets, if the user wants tests
203-- they should be able to debug failures.
204elseif sets == "tests" then
205	sets = sets
206-- If this is a -dbg package, put it in the -dbg subpackage of each set,
207-- which means the user can install debug symbols only for the sets they
208-- have installed.
209elseif pkgname:match("%-dbg$") then
210	local newsets = {}
211	for set in sets:gmatch("[^,]+") do
212		newsets[#newsets + 1] = set .. "-dbg"
213	end
214	sets = table.concat(newsets, ",")
215end
216-- Put our new sets back into the package.
217obj["annotations"]["set"] = sets
218
219-- If PKG_NAME_PREFIX is provided, rewrite the names of dependency packages.
220-- We can't do this in UCL since variable substitution doesn't work in array
221-- keys.
222if pkgprefix ~= nil and obj["deps"] ~= nil then
223	newdeps = {}
224	for dep, opts in pairs(obj["deps"]) do
225		local newdep = pkgprefix .. "-" .. dep
226		-- Make sure origin is set.
227		opts["origin"] = opts["origin"] or "base/"..newdep
228		newdeps[newdep] = opts
229	end
230	obj["deps"] = newdeps
231end
232
233-- Add comment and desc suffix.
234if no_suffix_pkgs[pkgname] == nil then
235	add_suffixes(obj)
236end
237
238-- Write the output file.
239local f,err = io.open(arg[#arg], "w")
240if not f then
241	io.stderr:write(arg[0] .. ": fail to open("..arg[#arg].."): ".. err)
242	os.exit(1)
243end
244
245f:write(ucl.to_format(obj, 'ucl', true))
246f:close()
247