%{lua: --[[------------------------------------------------------------------------------------------------ File: vdb.lua Copyright 🄯 2016, 2018—2023 Van de Bugger. SPDX-License-Identifier: GPL-3.0-or-later --]]------------------------------------------------------------------------------------------------ if not _vdb_loaded_ then --[[-- Some older rpms evaluate the file twice, so I added these guards. It looks like it is not required any more, at least in rpm 4.15.1. --]]-- --[[------------------------------------------------------------------------------------------------ String manipulation routines. --]]------------------------------------------------------------------------------------------------ --[[-- Substitution, or search and replace. `string.gsub` returns *two* results (the modified string and the number of replacements done), it may break code, e. g.: split( ' ', string.gsub( str, '\n', ' ' ) ) -- bang! `split` will receive 3 arguments, not 2! `subst` returns only one result, it makes nested calls safe: split( ' ', subst( str, '\n', ' ' ) ) --]]-- function subst( str, ... ) local subs = { ... } if # subs % 2 == 1 then push( subs, '' ) end local i = 1 while i < # subs do str = string.gsub( str, subs[ i ], subs[ i + 1 ] ) i = i + 2 end return str end -- Escapes % characters, returns string with all % characters doubled. function escape( str ) return subst( str, '%%', '%%%%' ) end -- Quotes string for using in shell scripts. function quote( str ) -- TODO: Do not quote string if it is does not contain spaces and special characters. return "'" .. subst( str, "'", "'\\''" ) .. "'" end -- Drops leading and trailing white space. function trim( str ) return subst( str, '^%s+', '', '%s+$', '' ) end -- Drops trailing newline, if any. function chomp( str ) return subst( str, '\n$', '' ) end -- Drops leading and trailing white space, and shrinks internal white space. function shrink( str ) return trim( subst( str, '%s+', ' ' ) ) end --[[-- Returns a concatenation of all hash values in order of their keys. Note the difference between join and table.concat: * table.concat process only keys in range [1 .. #hash], other keys are ignored. join process all keys. * table.concat can't process boolean values, join can. --]]-- function join( sep, hash ) -- table.concat doesn't like boolean values, so let's convert all values to strings: return table.concat( map( tostring, hash ), sep ) end -- Perl-like split: split string into items. function split( sep, str, limit ) if sep == ' ' then -- Special case. sep = '%s+' str = subst( str, '^' .. sep, '' ) end local list = {} if # str == 0 then -- One more special case. return list end limit = limit or 0 local strip = limit == 0 if limit <= 0 then limit = math.maxinteger end local pos = 1 repeat -- I have just one more slot: grab the rest of the string and exit the loop. if # list + 1 == limit then push( list, string.sub( str, pos ) ) break end -- Ok, I have some more slots, I can continue splitting. Try to find the next separator. local start, finish = string.find( str, sep, pos + ( # sep == 0 and 1 or 0 ) ) -- No more separators, grab the rest of the string and exit the loop. if start == nil then push( list, string.sub( str, pos ) ) break end -- Ok, the next separator found. Grab the piece of the string up to the next separator. push( list, string.sub( str, pos, start - 1 ) ) -- And move forward the current position behing the separator. pos = finish + 1 until false -- If limit is zero, drop trailing empty items: if strip then while # list > 0 and list[ # list ] == '' do pop( list ) end end return list end -- Perl-like uc: convert string to upper case. uc = string.upper -- Perl-like lc: convert string to lower case. lc = string.lower --[[-- Title case: convert the given string to lower case, then capitalize the first lettwer of every word. --]]-- function tc( str ) return subst( lc( str ), '^([^%s])', uc, '(%s[^%s])', uc ) end -- Perl-like sprintf. sprintf = string.format --[[-- Makes identifier (i. e. package name) from a string (e. g. program or font family name). The function replaces spaces with dashes and converts everything to lower case. Function accepts many arguments. e. g.: ident( 'Iosevka', 'Full', 'Ssans' ) -- returns 'iosevka-full-sans' --]]-- function ident( ... ) return lc( subst( join( ' ', { ... } ), ' ', '-' ) ) end --[[-- Prepends every line of text with prefix. --]]-- function prepend( prefix, text ) if text == '' then return text else local nl = string.sub( text, -1 ) == '\n' if nl then text = string.sub( text, 1, -2 ) end text = prefix .. subst( text, '\n', '\n' .. escape( prefix ) ) if nl then text = text .. '\n' end return text end end --[[-- Reflows text. Text may include multiple paragraphs separated by empty lines. Each paragraph will be reflowed. During reflow all leading and trailing spaces and tabs are dropped, spaces and tabs are shrinked to exactly one space, words will be (re)distributed between lines so each line does not exceed specified width (if possible). Current limitations are: * The function is not Unicode-aware. --]]-- function reflow( text, width ) text = subst( text, '^%s+', '', -- Drop leading whitespace in the text. '%s+$', '', -- Drop trailing whitespace in the text. '[ \t]+', ' ', -- Shrink spaces and tabs to one space. '\n ', '\n', -- Drop leading spaces in every line (except the first line). ' \n', '\n', -- Drop trailing spaces in every line (except the last line). '\n\n+', '\n\n' -- Shrink empty lines. ) width = width or 80 local paragraphs = {} for p, paragraph in ipairs( split( '\n\n', text ) ) do local words = split( ' ', paragraph ) local lines = {} -- Lines of the new paragraph. local line = '' -- Current line for w, word in ipairs( words ) do if # line == 0 then line = word elseif # line + 1 + # word <= width then line = line .. ' ' .. word else push( lines, line ) line = word end end if # line > 0 then push( lines, line ) end push( paragraphs, join( '\n', lines ) ) end text = join( '\n\n', paragraphs ) return text end -- Convert a value to string for debug purposes. function str( value ) local t = type( value ) if t == 'nil' then return 'nil' elseif t == 'boolean' or t == 'number' then return tostring( value ) elseif t == 'string' then return "'" .. subst( value, "[%c'\\]", function ( chr ) if chr == '\a' then return '\\a' elseif chr == '\b' then return '\\b' elseif chr == '\f' then return '\\f' elseif chr == '\n' then return '\\n' elseif chr == '\r' then return '\\r' elseif chr == '\t' then return '\\t' elseif chr == '\v' then return '\\v' elseif chr == '\\' then return '\\\\' elseif chr == "'" then return "\\'" end local result = '' for i, k in ipairs( table.pack( string.byte( chr, 1, -1 ) ) ) do result = result .. sprintf( '\\x%02X', k ) end return result end ) .. "'" elseif t == 'table' then local lines = {} push( lines, '{' ) for i, k in ipairs( keys( value ) ) do push( lines, prepend( ' ', sprintf( '%s → %s,', str( k ), str( value[ k ] ) ) ) ) end if # lines > 1 then push( lines, '}' ) else lines[ 1 ] = '{}' end return join( '\n', lines ) elseif t == 'function' then return tostring( value ) elseif t == 'userdata' then die( 'oops: userdata' ) else die( 'oops: unknown type: %s', t ) end end --[[------------------------------------------------------------------------------------------------ Array manipulation routines. --]]------------------------------------------------------------------------------------------------ -- Appends element(s) to the given array. function push( array, ... ) for i, v in ipairs( { ... } ) do table.insert( array, v ) end end -- Removes the last element from the given array and returns the removed element. function pop( array ) local value if # array > 0 then value = table.remove( array ) end return value end -- Removes the first element from the given array, and returns the removed element. function shift( array ) local value if # array > 0 then value = table.remove( array, 1 ) end return value end unpack = table.unpack --[[-- Sorts values of the given array in-place. It is table.sort with custom compare function. Default compare function can't compare boolean values, can't compare string with number, etc. Custom compare function works well with booleans, numbers and strings: booleans go first (false, then true), then go numbers (in natural order), then strings. This function should be local, but it made public for testing purposes. --]]-- function _sort( array ) _sort_order = { [ 'nil' ] = 0, [ 'boolean' ] = 1, [ 'number' ] = 2, [ 'string' ] = 3, [ 'table' ] = 4, [ 'function' ] = 5, [ 'thread' ] = 6, [ 'userdata' ] = 7, } table.sort( array, function ( a, b ) local ta = _sort_order[ type( a ) ] local tb = _sort_order[ type( b ) ] -- Lua can't compare values if their types differ, so let's make sure types if ta == tb then if ta == 1 then -- Lua can't compare boolean values return ( a and 1 or 0 ) < ( b and 1 or 0 ) end return a < b else return ta < tb end end ) return array end --[[------------------------------------------------------------------------------------------------ Hash manipulation routines. --]]------------------------------------------------------------------------------------------------ --[[-- Creates a hash. --]]-- function hash( keys, value ) if value == nil then value = false -- In Lua, nil value can't be stored in hash, let's use false instead. end local hash = {} for k, v in pairs( keys ) do hash[ v ] = value end return hash end --[[-- Returns sorted array of hash keys. --]]-- function keys( hash ) local keys = {} for k, v in pairs( hash ) do push( keys, k ) end return _sort( keys ) end --[[-- Returns array of hash values sorted by their keys. --]]-- function vals( hash ) local values = {} for i, k in ipairs( keys( hash ) ) do push( values, hash[ k ] ) end return values end --[[-- Returns sorted array of hash values. --]]-- function sort( hash ) --[[-- `table.sort` works on elements with keys in range [1 .. #table], other elements are ignored. Let's build array of hash values first, so all the values are sorted. --]]-- local values = {} for k, v in pairs( hash ) do push( values, v ) end return _sort( values ) end --[[-- Returns array, constructed from the given hash by applying the given function to each hash item (value and key), and pushing the function's result to the array. Hash items are enumerated in order defined by their sorted keys. --]]-- function map( func, hash ) local result = {} for i, k in ipairs( keys( hash ) ) do push( result, func( hash[ k ], k ) ) end return result end --[[-- Returns array, constructed from the given hash by applying the given pred function to each hash item (value and key), and pushing the hash value to the array if pred returned true for the item. If pred returned false, the item does not go into the array. Hash items are enumerated in order defined by their sorted keys. If pred is not a function, only values equal to pred go to the array. --]]-- function grep( pred, hash ) local func if type( pred ) == 'function' then func = pred else func = function ( it ) return it == pred; end end local result = {} for i, k in ipairs( keys( hash ) ) do if func( hash[ k ], k ) then push( result, hash[ k ] ) end end return result end --[[------------------------------------------------------------------------------------------------ RPM primitive routines. --]]------------------------------------------------------------------------------------------------ -- Expands rpm macros in the given string, returns result of expansion. expand = rpm.expand -- Returns true if rpm macro is defined, and false otherwise. function defined( name ) if rpm[ 'isdefined' ] then local defined, parametrized = rpm.isdefined( name ) return defined else return expand( '%{?' .. escape( name ) .. ':1}' ) == '1' end end --[[-- Define rpm macro. Note that rpm macros are stacked: (re)defining a macro does not simply overwrite the old value but pushes a new value on the stack. See also `unset`. --]]-- function set( name, value ) --[[-- If value is empty, rpm will complain on empty macro value. Let's use `%{nil}` in such a case to avoid warning. Also, RPM work with strings only, so let's convert Lua boolean values to strings. --]]-- if value == nil or value == '' then value = '%{nil}' elseif value == true then value = '1' elseif value == false then value = '0' end value = subst( value, '\\', '\\\\', -- Backslashes in macro body must be doubled. '\n', '\\\n' -- And newlines must be preceeded by backslashes. ) local l = name .. ' ' .. value spc( "%%define %s", l ) rpm.define( l ) return value end --[[-- Undefine rpm macro. Note that rpm macros are stacked: (re)defining a macro does not simply overwrite the old value but pushes a new value on the stack. Unsetting macro pops a value from the top of the stack, restoring the previous macro value. `count` — how many values to pop from the stack. By default 1. If 0, all the values will be popped, leaving the macro undefined. This function uses `rpm.undefine`, which is available starting from rpm 4.14.0. --]]-- function unset( name, count ) if count == nil then count = 1 end if count > 0 then while count > 0 do spc( "%%undefine %s", name ) rpm.undefine( name ) count = count - 1 end else while defined( name ) do spc( "%%undefine %s", name ) rpm.undefine( name ) end end end -- Get expanded value of rpm macro. If macro is not defined, default value is returned, and nil if -- default is not specified. Note, that resul type depends on default type: if default is number, -- result is converted to number too, if default is boolean, the result is converted to boolean -- too, otherwise result will be either string or nil. function get( name, default ) local result if defined( name ) then result = expand( '%{' .. escape( name ) .. '}' ) if type( default ) == 'number' then -- If default value is number, convert result to number. result = tonumber( result ) elseif type( default ) == 'boolean' then -- If default value is boolean, convert result to number. -- Note: empty string and '0' are treated as false, everything else — as true. result = not ( result == '' or result == '0' ) end end if result == nil then result = default end return result end -- Generate spec code. If the first parameter is table, it is interpreted as set of macros, which -- will be temporary defined. function spec( ... ) local lines = { ... } -- See if macros are present. local macros = {} if # lines > 0 and type( lines[ 1 ] ) == 'table' then macros = shift( lines ) end -- Set macros: for name, value in pairs( macros ) do set( name, value ) end -- Generate spec code: for i, line in ipairs( lines ) do local l = expand( line ) spc( "%s", l ) if # lines == 1 then print( l ) else print( l .. '\n' ) end end -- Restore original macro values: for name in pairs( macros ) do unset( name ) end end --[[------------------------------------------------------------------------------------------------ OS and filesystem routines. --]]------------------------------------------------------------------------------------------------ -- Get value of environment variable. If variable is not defined, default value is returned, and -- nil if default is not specified. Note, that resul type depends on default: if default is number, -- result is converted to number, if default is boolean, the result is converted to boolean, -- otherwise result will be either string or nil. function get_env( name, default ) local result result = posix.getenv( name ) if result then if type( default ) == 'number' then -- If default value is number, convert result to number. result = tonumber( result ) elseif type( default ) == 'boolean' then -- If default value is boolean, convert result to boolean. -- Note: empty string and '0' are treated as false, everything else — as true. result = not ( result == '' or result == '0' ) end end if result == nil then result = default end return result end --[[-- Set value of environment variable. If `value` is `nil`, the variable is removed. If the third argument is nil (e. g. it is omitted) or true, the existing environment variable is overwritten. If the third argument is false, the existing environment variable is not overwritten. --]]-- function set_env( name, value, overwrite ) if value == nil then posix.unsetenv( name ) else posix.setenv( name, value, overwrite == nil and true or overwrite ) end end -- Returns true if file exists. function exist( path ) return not not posix.stat( path ) end function which( file, list ) local dirs if list == nil then dirs = split( ':', get_env( 'PATH' ) ) elseif type( list ) == 'string' then dirs = split( ':', get_env( list ) ) elseif type( list ) == 'table' then dirs = list else die( "oops" ) end local i, dir, path for i, dir in ipairs( dirs ) do path = dir .. '/' .. file if exist( path ) then return path end end die( "Can't find file %s in directories %s", str( file ), join( ', ', map( str, dirs ) ) ) end -- Rename file or directory. function rename( old, new ) -- `+` mimics `rpmbuild` output. cmd( "mv %s %s", old, new ) local ok, error = os.rename( old, new ) if not ok then die( "mv: %s", error ) end return ok end --[[-- Read file. Read first `limit` lines of file. If limit is zero or nil, read all the lines, but drop empty lines at the end. If `limit` is negative, read all the lines. --]]-- function slurp( path, limit ) local lines = {} if limit == nil then limit = 0 end for line in io.lines( path ) do push( lines, line ) if limit > 0 and # lines >= limit then break end end if limit == 0 then while # lines > 0 and lines[ # lines ] == '' do pop( lines ) end end return lines end -- Write file. function spurt( path, ... ) local temp = path .. '.tmp' cmd( "> %s", temp ) local file, err = io.open( temp, 'w' ) if file == nil then die( "Can't open file %s for writing: %s", temp, err ) end local lines = { ... } for i, line in ipairs( lines ) do file, err = file:write( line .. '\n' ) if file == nil then die( "Can't write file %s: %s", temp, err ) end end -- TODO: Does close report errors? file:close() rename( temp, path ) end -- Execute shell command. function shell( command ) -- `+` mimics `rpmbuild` output. cmd( "%s", command ) local ok, status, code = os.execute( command ) if not ok then if status == 'exit' then die( "command exited with status %d", code ) else die( "command died with signal %d", code ) end end return ok end --[[------------------------------------------------------------------------------------------------ Logging routines. --]]------------------------------------------------------------------------------------------------ local function _say( stream, prefix, format, ... ) stream:write( prepend( prefix, sprintf( format, ... ) ) .. '\n' ) end -- Print a message to stderr if debug enabled. function dbg( format, ... ) if get( '_vdb_debug_', false ) then _say( io.stdout, '[#] ', format, ... ) end end -- Print a message to stderr if debug enabled. function spc( format, ... ) if get( '_vdb_debug_', false ) then _say( io.stdout, '[%] ', format, ... ) end end -- Print a message to stderr if debug enabled. function cmd( format, ... ) if get( '_vdb_debug_', false ) then _say( io.stdout, '[$] ', format, ... ) end end -- Print a variable. function dump( value, name ) if name == nil then dbg( "%s", str( value ) ) else dbg( "%s = %s", name, str( value ) ) end end -- Print a message to stderr. function say( format, ... ) _say( io.stdout, '[I] ', format, ... ) end function note( format, ... ) _say( io.stdout, '[N] ', format, ... ) end function warn( format, ... ) _say( io.stderr, '[W] ', format, ... ) end -- Print a error to stderr. function die( format, ... ) if format ~= nil then _say( io.stderr, '[E] ', format, ... ) end error( 'oops' ) end ---------------------------------------------------------------------------------------------------- -- Contruct a shell var assignment statement. Name is expected to be a valid name, it is converted -- to upper case. Value can be either string or table; value will be properly quoted. -- shell_assignment( 'foo', 'assa' ) -- returns "FOO='assa'" -- shell_assignment( 'foo', { 'a1', 'a2' } ) -- returns "FOO=('a1' 'a2')" local function shell_assignment( name, value ) if type( value ) == 'table' then if # value > 0 then return uc( name ) .. '=(\n ' .. join( '\n ', map( quote, value ) ) .. '\n)' else return uc( name ) .. '=()' end else return uc( name ) .. '=' .. quote( value ) end end -- Contruct a make var assignment statement. Name is expected to be valid name, value is also -- expected to be valid (no spces, no newlines, etc). -- make_syntax( 'foo', 'assa' ) -- returns "foo=assa" -- make_syntax( 'foo', { 'a1', 'a2' } ) -- returns "foo=a1 a2" local function make_assignment( name, value ) if type( value ) == 'table' then if # value > 0 then return name .. ' = \\\n ' .. join( ' \\\n ', value ) else return name .. ' =' end else return name .. ' = ' .. value end end --[[-- Write rpm package identification variables to the file. `name` — string, a name of environment variable containing name of file to write. If the environemnt variable is not set or empty, it is not error but file will not be written. `assignment` — function to construct assignment statements (either `shell_assignment` or `make_assignment`). --]]-- local function write_var_file( name, assignment ) local file = get_env( name, '' ) if file ~= '' then if type( _vdb_subpackages_ ) == 'table' then local bulk = { assignment( 'name', get( 'name' ) ), assignment( 'version', get( 'version' ) ), assignment( 'release', get( 'release' ) ), assignment( 'arch', get( '_target_cpu' ) ), assignment( 'rpmdir', get( '_rpmdir' ) ), assignment( 'srcrpmdir', get( '_srcrpmdir' ) ), assignment( 'subpackages', _vdb_subpackages_ ), assignment( 'sources', sources ), assignment( 'patches', patches ) } if not exist( file ) or str( slurp( file ) ) ~= str( bulk ) then -- Do not overwrite file if no changes in the content. spurt( file, unpack( bulk ) ) end else die( "OOPS" ) end end end local function banner( phase ) return ': ---------- ' .. phase .. ' ---------- :' end -- Prologue to execute in the beginning of every scriptlet: local prologue = 'set -e; [ -z "$BASH_VERSION" ] || set -o pipefail;' set( 'prologue', prologue ) function prep() write_var_file( 'SPEC_MK_FILE', make_assignment ) write_var_file( 'SPEC_RC_FILE', shell_assignment ) spec( get_env( 'SPEC_NO_PREP', false ) and ': SPEC_NO_PREP: exiting...; exit 0' or '', banner( 'PREP' ), prologue ) end function conf() spec( banner( 'CONF' ), prologue ) end function build() spec( banner( 'BUILD' ), prologue ) end function install() spec( banner( 'INSTALL' ), prologue ) end function check() spec( banner( 'CHECK' ), prologue ) end function clean() spec( banner( 'CLEAN' ), prologue ) end ---------------------------------------------------------------------------------------------------- -- Redefine %prep, %conf, %build, %install, %check, and %clean macros: -- Let's add prologue to the every build-time scriptlet. local phases = { 'prep', 'conf', 'build', 'install', 'check', 'clean' } for i, phase in ipairs( phases ) do set( phase, '%%' .. phase .. ' \n' .. '%{lua: ' .. phase .. '() }' ) end --[[------------------------------------------------------------------------------------------------ Let's add prologue to the every install-time scriptlet. --]]------------------------------------------------------------------------------------------------ -- Install-time scriptlets: local phases = { 'pretrans', 'pre', 'post', 'triggerin', 'triggerun', 'preun', 'postun', 'triggerpostun', 'posttrans', } for i, phase in ipairs( phases ) do if defined( phase ) then warn( "'%s' macro already defined", phase ) else -- If -p option present present, do not add prologue. set( phase .. '(p:)', '%%' .. phase .. ' %** \n' .. '%{!-p:%prologue}' ) end end --[[------------------------------------------------------------------------------------------------ Play the same trick to collect all subpackage names: This trick does not work for font packages in F38 and before: it requires small patch to `/lib/rpm/lua/fedora/srpm/fonts.lua`. --]]------------------------------------------------------------------------------------------------ -- If macro `package` is already defined, I can't redefine it and can't collect names of -- subpackages. However, do not throw error right now — subpackage names may be not needed. _vdb_subpackages_ = false if not defined( 'package' ) then _vdb_subpackages_ = {} set( -- %package macro may have -n option. If -n is present, it defines full name of the -- subpackage. Oterwise subpackage name is constructed from package name and the first -- macro argument. 'package(n:)', '%%package %**\n' .. [[%{lua: dbg( "%%package %s", get( '**' ) ) push( _vdb_subpackages_, get( '-n*' ) or get( 'name' ) .. '-' .. get( '1' ) ) }]] ) end --[[------------------------------------------------------------------------------------------------ Find out versions of external programs: --]]------------------------------------------------------------------------------------------------ -- Find out version of install program: function install_version() if _vdb_install_version_ == nil then local program = get( '__install' ) local output = rpm.expand( '%( %{__install} --version )' ) if output == '' then die( "Can't find version of '%s' program.", program ) end output = split( '\n', output, 2 )[ 1 ] -- Get the first line of output. local version = string.match( output, '^install %(GNU coreutils%) ([0-9.]+)$' ) if version == nil then die( "Can't parse '%s' program output: '%s'", program, output ) end dbg( "%s version %s", program, version ) _vdb_install_version_ = version end return _vdb_install_version_ end function find_rpm() local program = get( '__rpm' ) or ( get( '_bindir' ) .. '/rpm' ) -- In old systems (e. g. epel 6) rpm is in `/bin`, while `%{_bindir}` is `/usr/bin`, -- so let's also try to find `rpm` through `PATH` environment variable. if exist( program ) then return program end return which( 'rpm' ) end -- Find out version of rpm program: function rpm_version() if _vdb_rpm_version_ == nil then local program = find_rpm() local output = expand( '%( ' .. program .. ' --version )' ) if output == '' then die( "Can't find version of '%s' program.", program ) end output = split( '\n', output, 2 )[ 1 ] -- Get the first line of output. local version = string.match( output, '^RPM version ([-0-9a-z.]+)$' ) -- RPM version could be like `4.0.16` or `4.1.0-alpha1`. if version == nil then die( "Can't parse '%s' program output: '%s'", program, output ) end dbg( "%s version %s", program, version ) _vdb_rpm_version_ = version end return _vdb_rpm_version_ end --[[------------------------------------------------------------------------------------------------ Macros to use in spec files: --]]------------------------------------------------------------------------------------------------ --[[-- Usage: %{rem: Long multi-line comment. } --]]-- set( 'rem' ) --[[-- Usage: %Global NAME VALUE Defines global with specified NAME and VALUE, as well as lowercase version (both name and value) of the global. Example: %Global Font Hack # equivalent for # %global Font Hack # %global font hack --]]-- set( 'Global()', '%{lua: spec( ' .. "\"%global \" .. get( 1 ) .. \" \" .. get( 2 ), " .. "\"%global \" .. lc( get( 1 ) ) .. \" \" .. lc( get( 2 ) ) " .. ') }' ) --[[-- Usage: %description %{text -w -- \ Long description. Description may include multiple paragraphs. Description will be reflowed (see `reflow` function for details. } %global common_description %{text -- \ Long description. ... } where: * — integer number, desired max width of reformatted text. Option -w may be omitted, 80 will be used by default. --]]-- set( 'text(w:)', '%{lua: spec( reflow( expand( subst( get( "*" ), "^\\\\" ) ), get( "-w*", 80 ) ) ) }' -- Depending on usage (in %description body or in macro body) `get( '*' )` can return a -- string starting with `\` or not. Let's remove starting `\`. ) --[[-- Usage: %_install_D OPTION... ARGUMENT... Runs install program with -D and other specified options and arguments. Modern versions of install program treat -D option as instruction to create *all* the components of the target directory. However, older install in RHEL 6 and 7 treats -D option differently: it creates all the components of target directory *but* the last. _install_D macro emulates modern install program on RHEL 6 and 7. The macro recognizes only short options (e. g. `-t`), long options (e. g. `--target-directory`) are not supported. Also, currenly only `-m`, `-p`, and `-t` options are supported. Example: %install %_install_D -p -t %{buildroot}%{_bindir} build/bin/%{name} --]]-- local version = install_version() -- install 8.22 does not create the last component of target directory. -- install 8.29 does create all components. if rpm.vercmp( version, '8.23' ) > 0 then -- looks like vercmp returns -1, 0, and 1. -- install is recent enough to handle -D properly: dbg( "install %s: ok", version ) set( '_install_D', '%{__install} -D' ) else -- install is too old, create target directory before calling install: dbg( "install %s: too old", version ) -- Expanding two macros in adjacent lines: -- %_install_D -t dir0 file0 -- %_install-D -t dir1 file1 -- gives strange result in EPEL 6: -- mkdir -p dir0; install -D -t dir0 file0mkdir -p dir1; install -D -t dir1 file1 -- The macros are expanded to a single line with neither new line nor space between them. -- Other distros (EPEL 7, Fedora, Mageia, OpenSuse) do not have such a problem. -- Let's add a semicolon to the end of macro value to avoid error. set( '_install_D(m:pt:)', '%{-t:%{__mkdir_p} %{-t*}; }%{__install} -D %{-m:-m %{-m*}} %{-p:-p} %{-t:-t %{-t*}} %*; ' ) end --[[-- Usage: %if %{author_check} ... %else ... %endif %{author_check} expands to 0 (false) if SPEC_AUTHOR_CHECK environment variable is not set or is empty or is zero. Otherwise %{author_check} evaluates to 1 (true). Example: %check %if %{author_check} %{_bindir}/appstreamcli validate "%{name}.metainfo.xml" %endif --]]-- set( 'author_check', get_env( 'SPEC_AUTHOR_CHECK', false ) ) --[[------------------------------------------------------------------------------------------------ External programs and directories: --]]------------------------------------------------------------------------------------------------ if not defined( '__m4' ) then set( '__m4', '%{_bindir}/m4' ) end -- RHEL 7 has Python 3, but does not define __python3 macro. if not defined( '__python3' ) then set( '__python3', '%{_bindir}/python3' ) end if not defined( '__rpm' ) then set( '__rpm', find_rpm() ) end set( '_licensedir', '%{_datadir}/licenses' ) set( '_fontdir', '%{_datadir}/fonts' ) if not defined( '_pkgdocdir' ) then -- Fedora defines _pkgdocdir, but OpenSuse doesn't. set( '_pkgdocdir', '%{_docdir}/%{name}' ) end if not defined( '_metainfodir' ) then -- Mageia 6 does not define _metainfodir macro. -- As weel as OpenSuse 15.0, 15.1 and tumbleweed. set( '_metainfodir', '%{_datadir}/metainfo' ) end set_env( 'PS4', get_env( 'SPEC_PS4', '+ ' ) ) ---------------------------------------------------------------------------------------------------- --[[-- Platform detection: CentOS: %{centos} ("8"). EPEL: %{epel} ("7"). Fedora: %{fedora} ("34"). Fedora ELN: %{eln} (???). Mageia: %{mageia} ("9"). OpenMandriva: %{omvver} ("4003000" for rolling, "4050000" for cooker)?? %{mandriva_branch} ("Cooker" or "Rolling"). OpenSuse: %{suse_version} ("1500" for leap 15.2 and 15.3, "1550" for tumbleweed). --]]-- _vdb_loaded_ = 1 end } # end of file #