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