#95 Major rework of lua routines and forge macros
Closed 2 years ago by nim. Opened 2 years ago by nim.

file added
+239
@@ -0,0 +1,239 @@ 

+ -- Lua code used by macros.auto*

+ 

+ local fedora = require "fedora.common"

+ 

+ -- Executes f(k) for k in range, returning a table of ks for which

+ -- f(k) = value. Used to compute a pre_range for pfloop for example

+ local function fmatch(f, range, value)

+   local K = {}

+   for _, k in ipairs(range) do

+     if (f(k) == value) then

+       table.insert(K, k)

+     end

+   end

+   return K

+ end

+ 

+ -- Executes the <f> function in a loop

+ --   – the loop is controled by <index> (a string):

+ --     — if the <index> value given to floop is nil, loop over the whole

+ --       <range>. Use fedora.readflag() to process rpm flag arguments safely.

+ --     — otherwise execute <f> for the specified <index> value only, if

+ --       <index> is part of <range>

+ --   – <f> is passed <index> then <otherargs> as arguments.

+ --   – <pre_range> index values are processed before the rest of <range>.

+ local function pfloop(f, index, pre_range, range, otherargs)

+   local only  = {}

+   local first = {}

+   local last  = {}

+   for _, i in ipairs(range) do

+     if (index == nil) then

+       local pre = false

+       for _, p in ipairs(pre_range) do

+         if (i == p) then

+           pre = true

+           break

+         end

+       end

+       if pre then

+         table.insert(first, i)

+       else

+         table.insert(last, i)

+       end

+     elseif (i == index) then

+       table.insert(only, i)

+       break

+     end

+   end

+   for _, R in ipairs({only, first, last}) do

+     for _, i in ipairs(R) do

+       f(i, table.unpack(otherargs))

+     end

+   end

+ end

+ 

+ -- Executes the <f> function in a loop

+ --   – the loop is controled by <index> (a string):

+ --     — if the <index> value given to floop is nil, loop over the whole

+ --       <range>. Use fedora.readflag() to process rpm flag arguments safely.

+ --     — otherwise execute <f> for the specified <index> value only, if

+ --       <index> is part of <range>

+ --   – <f> is passed <index> then <otherargs> as arguments.

+ --   – <pre_range> index values are processed before the rest of <range>.

+ local function floop(f, index, range, otherargs)

+   pfloop(f, index, {}, range, otherargs)

+ end

+ 

+ -- Executes the <pkg> function in a loop, in a way compatible with package

+ -- header generation requirements

+ --   – the loop is controled by <index> (a string):

+ --     — if the <index> value given to floop is nil, loop over the whole

+ --       <range>. Use fedora.readflag() to process rpm flag arguments safely.

+ --     — otherwise execute <pkg> for the specified <index> value only, if

+ --       <index> is part of <range>

+ --   – <pkg> is passed <index> then <otherargs> as arguments.

+ --   – <name>(<index>) must return the %{name} of the SRPM header that would

+ --     be generated if <pkg>(<index>, <otherargs>) is called before something

+ --     else generates the SRPM header.

+ --   – if %{source_name} is set, <name>(<index>) results will be compared to

+ --     %{source_name} for all valid <index> in the loop. <index> values for

+ --     which that comparison matches will be scheduled first

+ local function pkg_floop(pkg, index, otherargs, name, range)

+   local source_range = {}

+   local  source_name = fedora.read("source_name")

+   if source_name then

+     source_range = fmatch(name, range, source_name)

+   end

+   pfloop(pkg, index, source_range, range, otherargs)

+ end

+ 

+ -- A small helper to return <name>(<index>) for all <index> values in <range>, since

+ -- pkg_floop() will usually be used in conjunction with pkg_exec()

+ local function names(name, range)

+   local N = {}

+   for _, i in ipairs(range) do

+     table.insert(N, name(i))

+   end

+   return N

+ end

+ 

+ local function registered(call)

+   local macros = {}

+   local auto_dir = fedora.read("_rpmautodir")

+   if auto_dir then

+     for filename in posix.files(auto_dir) do

+       if filename:match('%.auto$') then

+         local f = io.open(auto_dir .. "/" .. filename, "r")

+         for l in f:lines() do

+           local macro = l:match('^%%?' .. call ..'%s+%%?(%S+)')

+           if macro then

+             table.insert(macros, macro)

+           end

+         end

+       end

+     end

+   end

+   return macros

+ end

+ 

+ -- Executes all the macros in <macros>, passing -v as verbosity flag if

+ -- verbose. Each %<name> macro will be executed after the macros listed in the

+ -- %{<name>_after} variable (when those are also registered for the call).

+ -- Absent other constrains, %<name> macros for which %{<name>_last} is set to

+ -- anything else than false will be executed last

+ local function execute(macros, verbose)

+   local  found = {}

+   local  first = {}

+   local   last = {}

+   local   seen = {}

+   local   plan = {}

+   local      v = ""

+   if verbose then

+     v = " -v"

+   end

+   for _, macro in ipairs(macros) do

+     found[macro] = true

+     if fedora.readbool(macro .. "_last") then

+       table.insert(last, macro)

+     else

+       table.insert(first, macro)

+     end

+   end

+   local function insert(macro)

+     if seen[macro] then

+       return

+     end

+     seen[macro] = true

+     local deps = fedora.read(macro .. "_after")

+     if deps then

+       for dep in deps:gmatch("%S+") do

+         if found[dep] then

+           insert(dep)

+         end

+       end

+     end

+     table.insert(plan, macro)

+   end

+   for _, M in ipairs({first, last}) do

+     for _, macro in ipairs(M) do

+       insert(macro)

+     end

+   end

+   for _, macro in ipairs(plan) do

+     fedora.echo("%%" .. macro .. v)

+     print([[

+ #]] .. macro .. v ..

+ rpm.expand([[

+ 

+ %]]  .. macro .. v .. [[

+ ]]))

+   end

+ end

+ 

+ -- Executes all the macros registered for call, passing -v as verbosity flag if

+ -- verbose. Each %<name> macro will be executed after the macros listed in the

+ -- %{<name>_after} variable (when those are also registered for the call).

+ local function exec(call, verbose)

+   execute(registered(call), verbose)

+ end

+ 

+ -- Executes all the automated macros in the macros space-separated list of

+ -- <name>s, passing -v as verbosity flag if verbose.

+ -- This variant is intended to be used for package header generation purposes,

+ -- when those headers may include the SRPM header. To that effect, each %<name>

+ -- macro must have a %<name>_source_names counterpart that returns the list of

+ -- SRPM names that would be generated (if any), allowing <pkg_exec> to schedule

+ -- those that match %{source_name} first.

+ local function pkg_exec(call, verbose)

+   local source_name = fedora.read("source_name")

+   local      macros = registered(call)

+   local       first = {}

+   local        last = {}

+   local           v = ""

+   if verbose then

+     v = " -v"

+   end

+   for _, macro in ipairs(macros) do

+     local inserted = false

+     if source_name then

+       fedora.echo("%%" .. macro .. "_source_names")

+       local names = fedora.expand("%" .. macro .. "_source_names" .. v)

+       for name in names:gmatch("%S+") do

+         if name == source_name then

+           inserted = true

+           table.insert(first, macro)

+           break

+         end

+       end

+     end

+     if not inserted then

+       table.insert(last, macro)

+     end

+   end

+   if source_name and next(first) == nil then

+     table.insert(first, "buildsys_pkg")

+   end

+   local macros = {}

+   for _, M in ipairs({first, last}) do

+     for _, macro in ipairs(M) do

+       table.insert(macros, macro)

+     end

+   end

+   execute(macros, verbose)

+ end

+ 

+ -- A small helper to add function id to spec and console output, and keep track

+ -- of what is running when rpm barfs without providing any useful info

+ local function id(identifier)

+   fedora.echo("  " .. identifier)

+   print("#" .. identifier .. "\n")

+ end

+ 

+ return {

+   floop     = floop,

+   pkg_floop = pkg_floop,

+   names     = names,

+   exec      = exec,

+   pkg_exec  = pkg_exec,

+   id        = id,

+ }

file added
+1
@@ -0,0 +1,1 @@ 

+ auto_changelog buildsys_changelog

file added
+1
@@ -0,0 +1,1 @@ 

+ auto_sources buildsys_sources

file added
+156
@@ -0,0 +1,156 @@ 

+ -- buildsys macros routines, safe to use at SRPM build stage

+ 

+ local fedora = require "fedora.common"

+ 

+ local namespace = "buildsys"

+ local rads = {

+    ["start"] = {"name"},

+    ["evr"]   = {"epoch", "version", "release", "post_release"},

+    ["end"]   = {"summary", "license", "url", "tags", "description"},

+ }

+ 

+ local function radicals()

+   local R = {}

+   for k, v in pairs(rads) do

+     R[k] = v

+   end

+   local all = {}

+   for k, v in pairs(R) do

+     all = fedora.mergelists({all, v})

+   end

+   R["all"] = all

+   return R

+ end

+ 

+ local function srpm_pkg_start(verbose)

+   print(fedora.expand([[

+ Name:           %{source_name}

+ ]]))

+   -- Name: is declared, %{_sourcedir} can be used

+   local sn = "source"

+   local myrads = radicals()

+   fedora.mset(myrads["evr"], namespace, '', {

+                 fedora.mread(myrads["evr"], sn, '', false),

+               }, verbose, false)

+   local changelog = ''

+   local changelog_file = '%{_sourcedir}/%{' .. namespace .. '_changelog_file}'

+   local f = io.open(fedora.expand(changelog_file), "r")

+   if f then

+     for l in f:lines() do

+       changelog = changelog .. l .. "\n"

+     end

+     io.close(f)

+   end

+   fedora.set(namespace .. "_changelog_content", changelog, verbose)

+   print(fedora.expand([[

+ %{?buildsys_epoch:Epoch:          %{buildsys_epoch}}

+ Version:        %{buildsys_version}

+ Release:        %{buildsys_release}%{?dist}%{?buildsys_post_release:.%{buildsys_post_release}}]]))

+   -- Make sure the environment is ready for eventual pkg_end() execution

+   -- pkg_end is not srpm specific and does not operate from the source namespace

+   local cn = "__current_" .. namespace

+   fedora.mset(myrads["end"], cn, '', {

+                 fedora.mread(myrads["end"], sn, '', false),

+               }, verbose, true)

+   fedora.set("__" .. namespace .. "_srpm_done", true, verbose)

+ end

+ 

+ local function pkg_end()

+   local cn = "__current_" .. namespace

+   local itags = {}

+   for t, m in pairs({["Epoch"] = "epoch", ["Version"] = "version"}) do

+     local v = fedora.read(cn .. '_' .. m)

+     if v and (v ~= fedora.read('source_' .. m)) then

+       table.insert(itags, t .. ': ' .. v)

+     end

+   end

+   print(rpm.expand([[

+ 

+ Summary:        %{__current_buildsys_summary}

+ ]] .. table.concat(itags, '\n') .. [[

+ %{?__current_buildsys_license:License:        %{__current_buildsys_license}}

+ %{?__current_buildsys_url:URL:            %{__current_buildsys_url}}

+ %{?__current_buildsys_tags}

+ %description -n %{__current_buildsys_name}

+ %wordwrap    -v   __current_buildsys_description

+ ]]))

+ end

+ 

+ -- %new_package core

+ local function new_package(pkg_name, name_suffix, verbose)

+   local sn = "source"

+   local cn = "__current_" .. namespace

+   local source_name = fedora.read(sn .. "_name")

+   -- Safety net when the wrapper is used in conjunction with traditional syntax

+   if fedora.read("name") and (not source_name) then

+     warning([[

+ Something already set a package name. However, %%{source_name} is not set.

+ Please set %%{source_name} to the SRPM name to ensure reliable processing.

+ ]])

+     if name_suffix then

+       print(fedora.expand("%package        " .. name_suffix))

+     else

+       print(fedora.expand("%package     -n " .. pkg_name))

+     end

+     return

+   end

+   -- New processing

+   -- We can not rely on cn .. "_name" because new_package (unlike pkg)

+   -- does not require users to clear past environement between calls

+   if not (pkg_name or name_suffix or source_name) then

+     fedora.error(

+       "You need to set %%{source_name} or provide explicit package naming!")

+   end

+   local first = not fedora.read("__" .. namespace .. "_srpm_done")

+   if name_suffix then

+     pkg_name = "%{source_name}-" .. name_suffix

+     print(fedora.expand("%package        "  .. name_suffix))

+   else

+     if first and not source_name then

+       source_name = pkg_name

+       fedora.safeset(sn .. "_name", source_name, verbose)

+     end

+     if not pkg_name then

+       pkg_name = source_name

+     end

+     if (pkg_name == source_name) then

+       local myrads = radicals()

+       -- Collect bits that may have been declared in the __current_

+       -- namespace. srpm_pkg_start will take the source namespace as only

+       -- trusted reference

+       fedora.mset(myrads["all"], sn, "", {

+           -- cn is a temporary reusable ref, all mreads must resolve

+           fedora.mread({"epoch", "version"}, cn, '', true),

+           fedora.mread(myrads["end"], cn, '', true),

+         }, verbose, false)

+       srpm_pkg_start()

+     else

+       print(fedora.expand("%package     -n " .. pkg_name))

+     end

+   end

+   _ = fedora.suffix({[namespace .. "_name"] = pkg_name}, true, verbose)

+   local cn = "__current_" .. namespace

+   fedora.set(cn .. "_name", pkg_name, verbose)

+ end

+ 

+ local function pkg(verbose)

+   local cn = "__current_" .. namespace

+   local pkg_name = fedora.read(cn .. "_name")

+   new_package(pkg_name, nil, verbose)

+   pkg_end()

+ end

+ 

+ local function reset(verbose)

+   local cn = "__current_" .. namespace

+   local myrads = radicals()

+   fedora.unset("__buildsys_srpm_done", verbose)

+   fedora.mset(myrads["all"], cn, '', {{}}, verbose, true)

+ end

+ 

+ return {

+   namespace   = namespace,

+   radicals    = radicals()['all'],

+   new_package = new_package,

+   pkg         = pkg,

+   reset       = reset,

+ }

file modified
+517 -194
@@ -1,17 +1,77 @@ 

  -- Convenience Lua functions that can be used within rpm macros

  

- -- Reads an rpm variable. Unlike a basic rpm.expand("{?foo}"), returns nil if

- -- the variable is unset, which is convenient in lua tests and enables

- -- differentiating unset variables from variables set to ""

- local function read(rpmvar)

+ -- Reads an rpm variable reference. Returns "%{" .. rpmvar.. "}" if rpmvar is

+ -- set to something, and nil otherwise. This is convenient in lua tests and to

+ -- distinguish between an unset variable and a variable set to something that

+ -- expands to "" at this point of the spec file.

+ local function ref(rpmvar)

    if not rpmvar or

      (rpm.expand("%{" .. rpmvar .. "}") == "%{" .. rpmvar .. "}") then

      return nil

    else

-     return rpm.expand("%{?" .. rpmvar .. "}")

+     return "%{" .. rpmvar .. "}"

    end

  end

  

+ -- Expands <run> till there is no expansion left to do

+ local function expand(run)

+   if not run then

+     return nil

+   end

+   run = run:gsub("%%%%", "\029")

+   local expanded = rpm.expand(run)

+   while run ~= expanded do

+     run = expanded

+     expanded = rpm.expand(run)

+   end

+   expanded = expanded:gsub("\029", "%%%%")

+   return expanded

+ end

+ 

+ -- Reads an rpm variable (reads the variable reference, and evaluates it).

+ -- Unlike a basic rpm.expand("{?" .. rpmvar .. "}"), returns nil if the

+ -- variable is unset, which is convenient in lua tests.

+ local function read(rpmvar)

+   local ref = ref(rpmvar)

+   if ref then

+     return expand(ref)

+   else

+     return ref

+   end

+ end

+ 

+ -- Sometimes it is convenient to distinguish between an unset variable and a

+ -- variable explicitly set to false. Unlike read, readbool returns a tristate,

+ -- true, false or nil

+ local function readbool(rpmvar)

+   local value = read(rpmvar)

+   if value then

+     if (value:lower() == "false") then

+       return false

+     else

+       return true

+     end

+   else

+     return value

+   end

+ end

+ 

+ -- Builds a list of un-commented and non-empty lines from a multiline rpm

+ -- variable. This allows using multiline expands for private %sourcelist-like

+ -- containers.

+ local function readlines(rpmvar)

+   local lines = read(rpmvar)

+   local L = {}

+   if lines then

+     for line in string.gmatch(lines,'[^\r\n]+') do

+       if string.match(line, '^[^#]') then

+         table.insert(L, line)

+       end

+     end

+   end

+   return L

+ end

+ 

  -- Returns true if the macro that called this function had flag set

  --   – for example, hasflag("z") would give the following results:

  --     %foo -z bar → true
@@ -40,114 +100,420 @@ 

    end

  end

  

- -- Sets a spec variable; echoes the result if verbose

- local function explicitset(rpmvar, value, verbose)

-   local value = value

-   if (value == nil) or (value == "") then

-     value = "%{nil}"

+ -- %wordwrap core

+ local function wordwrap(text)

+   text = expand(text .. "\n")

+   text = text:gsub("%%%%", "\029")

+   text = text:gsub("\t",               "  ")

+   text = text:gsub("\r",               "\n")

+   text = text:gsub(" +\n",             "\n")

+   text = text:gsub("\n+\n",            "\n\n")

+   text = text:gsub("^\n",              "")

+   text = text:gsub("\n( *)[-*—][  ]+", "\n%1– ")

+   output = ""

+   for line in text:gmatch("[^\n]*\n") do

+     local pos = 0

+     local advance = ""

+     for word in line:gmatch("%s*[^%s]*\n?") do

+       local wl, bad = utf8.len(word)

+       if not wl then

+         -- Can not use warning in wordwrap since warning uses wordwrap

+         rpm.expand([[

+ %{warn:Invalid UTF-8 sequence detected in:}

+ %{warn:]] .. word .. [[}

+ %{warn:It may produce unexpected results.}

+ ]])

+         wl = bad

+       end

+       if (pos == 0) then

+         advance, n = word:gsub("^(%s*– ).*", "%1")

+         if (n == 0) then

+           advance  = word:gsub("^(%s*).*",   "%1")

+         end

+         advance = advance:gsub("– ", "  ")

+         pos = pos + wl

+       elseif  (pos + wl  < 81) or

+              ((pos + wl == 81) and word:match("\n$")) then

+         pos = pos + wl

+       else

+         word = advance .. word:gsub("^%s*", "")

+         output = output .. "\n"

+         pos = utf8.len(word)

+       end

+       output = output .. word

+       if pos > 80 then

+         pos = 0

+         if not word:match("\n$") then

+           output = output .. "\n"

+         end

+       end

+     end

    end

-   rpm.define(rpmvar .. " " .. value)

-   if verbose then

-     rpm.expand("%{warn:Setting %%{" .. rpmvar .. "} = " .. value .. "}")

+   output = output:gsub("^\n*", "")

+   output = output:gsub("\n*$", "\n")

+   -- Escape %’s – we want macros that were preserved from wordwrap expansion to

+   -- be preserved at the next stage too

+   output = output:gsub("\029", "%%%%")

+   return output

+ end

+ 

+ -- Reformats text and outputs it using a built-in rpm verb such as echo, warn

+ -- or error

+ local function message(verb, text)

+   rpm.expand("%{" .. verb .. ":" .. wordwrap(text):gsub("\n*$", "") .. "}")

+ end

+ 

+ -- Writes some text to stdout

+ local function echo(text)

+   message("echo", text)

+ end

+ 

+ -- Writes the list of z-suffixed rpmvars to the console, if set

+ local function echovars(rpmvars, z)

+   for _, rpmvar in ipairs(rpmvars) do

+     local suffixed = rpmvar .. z

+     local header = string.sub("  " .. suffixed .. ":                                               ",1,24)

+     local v = ref(suffixed)

+     if v then

+       echo(header .. v)

+     end

    end

  end

  

- -- Unsets a spec variable if it is defined; echoes the result if verbose

- local function explicitunset(rpmvar, verbose)

-   if (rpm.expand("%{" .. rpmvar .. "}") ~= "%{" .. rpmvar .. "}") then

-     rpm.define(rpmvar .. " %{nil}")

-     if verbose then

-       rpm.expand("%{warn:Unsetting %%{" .. rpmvar .. "}}")

+ -- Writes some text as a warning

+ local function warning(text)

+   message("warn", text)

+ end

+ 

+ -- Writes some text as an error

+ local function err(text)

+   message("error", text)

+ end

+ 

+ -- Returns a table of <radical> = <value>, for all <radical>s in <radicals>,

+ -- with <value> = reference of <namespace>_<radical><suffix> if

+ -- <namespace>_<radical><suffix> is set. Radicals for which

+ -- <namespace>_<radical><suffix> is not set are omitted in results.

+ local function mread(radicals, namespace, suffix, resolve)

+   local R = {}

+   for _, radical in ipairs(radicals) do

+     local name = radical

+     if namespace and (namespace ~= "") then

+       name = namespace .. "_" .. name

+     end

+     local ref = ref(name .. suffix)

+     if ref then

+       if resolve then

+         ref = expand(ref)

+       end

+       R[radical] = ref

+     end

+   end

+   return R

+ end

+ 

+ -- Sets a spec variable; echoes the result if verbose

+ local function set(rpmvar, value, verbose)

+   if rpmvar then

+     -- Empty the stack state

+     while read(rpmvar) do

+       rpm.undefine(rpmvar)

+     end

+     if (value == nil) then

+       if ref(rpmvar) then

+         if verbose then

+           -- Already did it, white lie

+           warning("Unsetting " .. rpmvar)

+         end

+       end

+     else

+       if (value == false) then

+         value = "false"

+       elseif (value == true) then

+         value = "true"

+       end

+       if verbose then

+         warning("Setting "  .. rpmvar .. " = " .. value)

+       end

+       rpm.define(rpmvar .. " %{expand:" .. value .. "}")

      end

    end

  end

  

+ -- Unsets a spec variable if it is defined; echoes the result if verbose

+ local function unset(rpmvar, verbose)

+   set(rpmvar, nil, verbose)

+ end

+ 

  -- Sets a spec variable, if not already set; echoes the result if verbose

  local function safeset(rpmvar, value, verbose)

-   if (rpm.expand("%{" .. rpmvar .. "}") == "%{" .. rpmvar .. "}") then

-     explicitset(rpmvar,value,verbose)

+   if not ref(rpmvar) then

+     set(rpmvar, value, verbose)

    end

  end

  

- -- Aliases a list of rpm variables to the same variables suffixed with 0 (and

- -- vice versa); echoes the result if verbose

- local function zalias(rpmvars, verbose)

-   for _, sfx in ipairs({{"","0"},{"0",""}}) do

-     for _, rpmvar in ipairs(rpmvars) do

-       local toalias = "%{?" .. rpmvar .. sfx[1] .. "}"

-       if (rpm.expand(toalias) ~= "") then

-         safeset(rpmvar .. sfx[2], toalias, verbose)

+ -- Sets the <namespace>_<radical><suffix> variable for all <radical>s in

+ -- <radicals>, to a <value> taken in the <stack> list of <radical> = <value>

+ -- tables. When <radical> exists in multiple tables, only its first <value> is

+ -- taken into account. When <radical> does not exist in any table, no

+ -- <namespace>_<radical><suffix> is set.

+ -- <stack> is usually constructed with multiple mread calls, taking

+ -- domain-specific fallback needs into consideration.

+ -- If <force> set via set (and unset variables with no matches), otherwise set

+ -- via safeset.

+ -- mset is a complex function; its specializations like alias, bialias, zalias

+ -- and set_current are sufficient for many needs.

+ local function mset(radicals, namespace, suffix, stack, verbose, force)

+   for _, radical in ipairs(radicals) do

+     local value = nil

+     local found = false

+     for _, values in ipairs(stack) do

+       for k, v in pairs(values) do

+         if k == radical then

+           value = v

+           found = true

+           break

+         end

+       end

+       if found then

+         break

        end

      end

+     local name = radical

+     if namespace and (namespace ~= "") then

+       name = namespace .. "_" .. name

+     end

+     if force then

+       set(    name .. suffix, value, verbose)

+     else

+       safeset(name .. suffix, value, verbose)

+     end

    end

  end

  

- -- Takes a list of rpm variable roots and a suffix and alias current<root> to

- -- <root><suffix> if it resolves to something not empty

- local function setcurrent(rpmvars, suffix, verbose)

-   for _, rpmvar in ipairs(rpmvars) do

-     if (rpm.expand("%{?" .. rpmvar .. suffix .. "}") ~= "") then

-       explicitset(  "current" .. rpmvar, "%{" .. rpmvar .. suffix .. "}", verbose)

-     else

-       explicitunset("current" .. rpmvar,                                  verbose)

+ -- Sets the <namespace>_<radical><suffix> variable for all <radical>s in

+ -- <radicals>, to a reference to <from_namespace>_<radical><from_suffix>,

+ -- if <from_namespace>_<radical><from_suffix> is set.

+ -- inherit_from is recursive: the next <from_suffix> is read in

+ -- <from_namespace>_<from_key><from_suffix>

+ local function inherit_from (radicals, namespace, suffix, from_namespace,

+                              from_suffix, from_key, verbose)

+   local seen  = {}

+   local stack = {}

+   local function icopy(z)

+     if not z or seen[z] then

+       return

+     end

+     seen[z] = true

+     table.insert(stack, mread(radicals, from_namespace, z, false))

+     if from_key and (from_key ~= "") then

+       local new_key = from_key .. z

+       if from_namespace and (from_namespace ~= "") then

+         new_key = from_namespace .. "_" .. new_key

+       end

+       icopy(read(new_key))

      end

    end

+   icopy(from_suffix)

+   mset(radicals, namespace, suffix, stack, verbose, false, false)

  end

  

- -- Echo the list of rpm variables, with suffix, if set

- local function echovars(rpmvars, suffix)

-   for _, rpmvar in ipairs(rpmvars) do

-     rpmvar = rpmvar .. suffix

-     local header = string.sub("  " .. rpmvar .. ":                                               ",1,21)

-     rpm.expand("%{?" .. rpmvar .. ":%{echo:" .. header .. "%{?" .. rpmvar .. "}}}")

+ -- Sets the <namespace>_<radical><suffix> variable for all <radical>s in

+ -- <radicals>, to a reference to <namespace>_<radical><from_suffix>,

+ -- if <namespace>_<radical><from_suffix> is set.

+ -- <from_suffix> is read in <namespace>_<from_key><suffix>

+ -- inherit is recursive, and will also process <from_key> at the next level

+ local function inherit(radicals, namespace, suffix, key, verbose)

+   local k = key .. suffix

+   if namespace and (namespace ~= "") then

+     k = namespace .. "_" .. k

    end

+   local from_suffix = read(k)

+   inherit_from(radicals, namespace, suffix, namespace, from_suffix, key,

+                verbose)

  end

  

- -- Returns an array, indexed by suffix, containing the non-empy values of

- -- <rpmvar><suffix>, with suffix an integer string or the empty string

- local function getsuffixed(rpmvar)

-   local suffixes = {}

-   zalias({rpmvar})

-   for suffix=0,9999 do

-     local value = rpm.expand("%{?" .. rpmvar .. suffix .. "}")

-     if (value ~= "") then

-       suffixes[tostring(suffix)] = value

+ -- for each radical in radicals, safeset <to_namespace><radical><to_suffix>

+ -- to %{<from_namespace><radical><from_suffix>} if

+ -- <from_namespace><radical><from_suffix> is set to something

+ local function alias(radicals, from_namespace, from_suffix,

+                                to_namespace,   to_suffix,   verbose)

+   mset(radicals, to_namespace, to_suffix,

+         {mread(radicals, from_namespace, from_suffix, false)},

+         verbose, false)

+ end

+ 

+ -- Alias back and forth

+ local function bialias(radicals, namespace1, suffix1,

+                                  namespace2, suffix2, verbose)

+   alias(radicals, namespace1, suffix1, namespace2, suffix2, verbose)

+   alias(radicals, namespace2, suffix2, namespace1, suffix1, verbose)

+ end

+ 

+ -- Aliases a list of rpm variables to the same variables suffixed with 0 (and

+ -- vice versa); echoes the result if verbose

+ local function zalias(rpmvars, verbose)

+   bialias(rpmvars, "", "", "",  "0", verbose)

+ end

+ 

+ -- Takes a list of rpm variable roots and a suffix and alias __current_<rpmvar>

+ -- to <rpmvar><suffix> if it resolves to something not empty

+ local function set_current(rpmvars, suffix, verbose)

+   mset(rpmvars, "__current", "",

+        {mread(rpmvars, "", suffix, false)}, verbose, true)

+ end

+ 

+ -- Sets the usual verbosity variables in a generic way

+ local function set_verbose(verbose)

+   if verbose then

+     set(  "__current_verbose",       "-v",     verbose)

+     unset("__current_quiet",                   verbose)

+     unset("__current_shell_quiet",             verbose)

+     unset("__current_shell_verbose",           verbose)

+   else

+     unset("__current_verbose",                 verbose)

+     set(  "__current_quiet",         "-q",     verbose)

+     set(  "__current_shell_quiet",   "set +x", verbose)

+     set(  "__current_shell_verbose", "set -x", verbose)

+   end

+ end

+ 

+ -- Returns an array of <namespace>_<radical>

+ local function qualify(radicals, namespace)

+   local R = {}

+   if (namespace ~= "") then

+     namespace = namespace .. "_"

+   end

+   for _, radical in ipairs(radicals) do

+     table.insert(R, namespace .. radical)

+   end

+   return R

+ end

+ 

+ -- Merges two lists, removing duplicates and ordering the result

+ -- The result is a list containing an ordered set of values

+ local function mergelists(list_of_lists)

+   local S = {}

+   local L = {}

+   for   _, l in ipairs(list_of_lists) do

+     for _, v in ipairs(l) do

+       S[v] = true

+     end

+   end

+   for k, _ in  pairs(S) do

+     table.insert(L, k)

+   end

+   table.sort(L)

+   return L

+ end

+ 

+ -- Returns a list of suffixes, for which <rpmvar><suffix> is set to something

+ local function suffixes(rpmvar)

+   local S = {}

+   for z=0,9999 do

+     if ref(rpmvar .. z) then

+       table.insert(S, tostring(z))

      end

    end

    -- rpm convention is to alias no suffix to zero suffix

    -- only add no suffix if zero suffix is different

-   local value = rpm.expand("%{?" .. rpmvar .. "}")

-   if (value ~= "") and (value ~= suffixes["0"]) then

-      suffixes[""] = value

+   local value = read(rpmvar)

+   if value and not (value == read(rpmvar .. "0")) then

+     table.insert(S, "")

    end

-   return suffixes

+   return S

  end

  

  -- Returns the list of suffixes, including the empty string, for which

- -- <rpmvar><suffix> is set to a non empty value

- local function getsuffixes(rpmvar)

-   suffixes = {}

-   for suffix in pairs(getsuffixed(rpmvar)) do

-     table.insert(suffixes,suffix)

+ -- <rpmvar><suffix> is set to a non empty value, for any rpmvar in rpmvars

+ local function all_suffixes(rpmvars)

+   local SL = {}

+   local S  = {}

+   for _, rpmvar in ipairs(rpmvars) do

+     table.insert(SL, suffixes(rpmvar))

    end

-   table.sort(suffixes,

+   S = mergelists(SL)

+   table.sort(S,

               function(a,b) return (tonumber(a) or 0) < (tonumber(b) or 0) end)

-   return suffixes

+   return S

  end

  

- -- Returns the suffix for which <rpmvar><suffix> has a non-empty value that

- -- matches best the beginning of the value string

- local function getbestsuffix(rpmvar, value)

-   local best         = nil

-   local currentmatch = ""

-   for suffix, setvalue in pairs(getsuffixed(rpmvar)) do

-   if (string.len(setvalue) > string.len(currentmatch)) and

-      (string.find(value, "^" .. setvalue)) then

-       currentmatch = setvalue

-       best         = suffix

+ local function suffix_new(set, verbose)

+   local s = 0

+   local used = false

+   repeat

+     z = tostring(s)

+     used = false

+     for k, _ in pairs(set) do

+       used = used or ref(k .. z)

      end

+     s = s + 1

+   until (not used)

+   for k, v in pairs(set) do

+     safeset(k .. z, v, verbose)

    end

-   return best

+   return z

+ end

+ 

+ local function suffix_reuse(set, verbose)

+   for s = 0, 9999 do

+     z = tostring(s)

+     local matches = true

+     for k, v in pairs(set) do

+       matches = matches and (read(k .. z) == v)

+     end

+     if matches then

+       return z

+     end

+   end

+   return suffix_new(set, verbose)

+ end

+ 

+ -- Takes a set[key] = <value> table and returns a suffix for which all the

+ -- <key><suffix> rpm variables have the corresponding <value> set.

+ -- If reuse attempts to find an existing suffix that matches, otherwise,

+ -- takes an available suffix and creates the variable set from scratch

+ local function suffix(set, reuse, verbose)

+   if reuse then

+     return suffix_reuse(set, verbose)

+   else

+     return suffix_new(set, verbose)

+   end

+ end

+ 

+ -- Returns the suffix/number of a source/patch filename

+ -- We could do it with source_num/patch_num but those are dependant on a

+ -- specific rpm version and are not available for all the other variables we

+ -- use suffixes() with

+ local function sourcedir_suffix(filename, radical)

+   if not filename then

+     return nil

+   end

+   filename = expand(filename)

+   if not filename then

+     return nil

+   end

+   filename = filename:match("[^/]+$")

+   if not filename then

+     return nil

+   end

+   filename = expand("%{_sourcedir}/" .. filename)

+   for _, z in ipairs(suffixes(radical)) do

+     if expand('%{' .. radical .. z .. "}") == filename then

+       return z

+     end

+   end

+   return nil

+ end

+ 

+ -- Returns the source suffix/number of a filename

+ local function source_suffix(filename)

+   return sourcedir_suffix(filename, "SOURCE")

+ end

+ 

+ -- Returns the patch suffix/number of a filename

+ local function patch_suffix(filename)

+   return sourcedir_suffix(filename, "PATCH")

  end

  

  -- %writevars core
@@ -159,136 +525,93 @@ 

    end

  end

  

- -- https://github.com/rpm-software-management/rpm/issues/566

- -- Reformat a text intended to be used used in a package description, removing

- -- rpm macro generation artefacts.

- -- – remove leading and ending empty lines

- -- – trim intermediary empty lines to a single line

- -- – fold on spaces

- -- Should really be a %%{wordwrap:…} verb

- local function wordwrap(text)

-   text = rpm.expand(text .. "\n")

-   text = string.gsub(text, "\t",              "  ")

-   text = string.gsub(text, "\r",              "\n")

-   text = string.gsub(text, " +\n",            "\n")

-   text = string.gsub(text, "\n+\n",           "\n\n")

-   text = string.gsub(text, "^\n",             "")

-   text = string.gsub(text, "\n( *)[-*—][  ]+", "\n%1– ")

-   output = ""

-   for line in string.gmatch(text, "[^\n]*\n") do

-     local pos = 0

-     local advance = ""

-     for word in string.gmatch(line, "%s*[^%s]*\n?") do

-       local wl, bad = utf8.len(word)

-       if not wl then

-         print("%{warn:Invalid UTF-8 sequence detected in:}" ..

-               "%{warn:" .. word .. "}" ..

-               "%{warn:It may produce unexpected results.}")

-         wl = bad

-       end

-       if (pos == 0) then

-         advance, n = string.gsub(word, "^(%s*– ).*", "%1")

-         if (n == 0) then

-           advance = string.gsub(word, "^(%s*).*", "%1")

-         end

-         advance = string.gsub(advance, "– ", "  ")

-         pos = pos + wl

-       elseif  (pos + wl  < 81) or

-              ((pos + wl == 81) and string.match(word, "\n$")) then

-         pos = pos + wl

-       else

-         word = advance .. string.gsub(word, "^%s*", "")

-         output = output .. "\n"

-         pos = utf8.len(word)

-       end

-       output = output .. word

-       if pos > 80 then

-         pos = 0

-         if not string.match(word, "\n$") then

-           output = output .. "\n"

-         end

-       end

+ 

+ 

+ -- deprecated graveyard

+ -- those can be removed as soon as a port of fonts-rpm-macros and go-rpm-macros

+ -- to the new API lands in koji

+ 

+ local function setcurrent(rpmvars, suffix, verbose)

+   warning("setcurrent is deprecated, replace it with set_current")

+   for _, rpmvar in ipairs(rpmvars) do

+     local suffixed = rpmvar .. suffix

+     if ref(suffixed) then

+       set(  "current" .. rpmvar, "%{?" .. suffixed .. "}", verbose)

+     else

+       unset("current" .. rpmvar,                           verbose)

      end

    end

-   output = string.gsub(output, "\n*$", "\n")

-   return output

  end

  

- -- Because rpmbuild will fail if a subpackage is declared before the source

- -- package itself, provide a source package declaration shell as fallback.

- local function srcpkg(verbose)

-   if verbose then

-     rpm.expand([[

- %{echo:Creating a header for the SRPM from %%{source_name}, %%{source_summary} and}

- %{echo:%%{source_description}. If that is not the intended result, please declare the}

- %{echo:SRPM header and set %%{source_name} in your spec file before calling a macro}

- %{echo:that creates other package headers.}

- ]])

-   end

-   print(rpm.expand([[

- Name:           %{source_name}

- Summary:        %{source_summary}

- %description

- %wordwrap -v source_description

- ]]))

-   explicitset("currentname", "%{source_name}", verbose)

- end

- 

- -- %new_package core

- local function new_package(source_name, pkg_name, name_suffix, first, verbose)

-   -- Safety net when the wrapper is used in conjunction with traditional syntax

-   if (not first) and (not source_name) then

-     rpm.expand([[

- %{warn:Something already set a package name. However, %%{source_name} is not set.}

- %{warn:Please set %%{source_name} to the SRPM name to ensure reliable processing.}

- ]])

-     if name_suffix then

-       print(rpm.expand("%package        " .. name_suffix))

-     else

-       print(rpm.expand("%package     -n " .. pkg_name))

+ local function getsuffixed(rpmvar)

+   local S = {}

+   zalias({rpmvar}, false)

+   for z=0,9999 do

+     local value = read(rpmvar .. z)

+     if value then

+       S[tostring(z)] = value

      end

-     return

    end

-   -- New processing

-   if not (pkg_name or name_suffix or source_name) then

-     rpm.expand([[

- %{error:You need to set %%{source_name} or provide explicit package naming!}

- ]])

+   -- rpm convention is to alias no suffix to zero suffix

+   -- only add no suffix if zero suffix is different

+   local value = read(rpmvar)

+   if value and (value ~= S["0"]) then

+     S[""] = value

    end

-   if name_suffix then

-     print(rpm.expand("%package        "  .. name_suffix))

-     explicitset("currentname", "%{source_name}-" .. name_suffix, verbose)

-   else

-     if not source_name then

-       source_name = pkg_name

-     end

-     if (pkg_name == source_name) then

-       safeset("source_name", source_name, verbose)

-       print(rpm.expand("Name:           %{source_name}"))

-     else

-       if source_name and first then

-         srcpkg(verbose)

-       end

-       print(rpm.expand("%package     -n " .. pkg_name))

+   return S

+ end

+ 

+ local function getbestsuffix(rpmvar, value)

+   local best         = nil

+   local currentmatch = ""

+   for z, setvalue in pairs(getsuffixed(rpmvar)) do

+   if ((not best) or (string.len(setvalue) < string.len(currentmatch))) and

+      (string.find(setvalue, "^" .. value)) then

+       currentmatch = setvalue

+       best         = z

      end

-     explicitset("currentname", pkg_name, verbose)

    end

+   return best

  end

  

  return {

-   read          = read,

-   hasflag       = hasflag,

-   readflag      = readflag,

-   explicitset   = explicitset,

-   explicitunset = explicitunset,

-   safeset       = safeset,

-   zalias        = zalias,

-   setcurrent    = setcurrent,

-   echovars      = echovars,

-   getsuffixed   = getsuffixed,

-   getsuffixes   = getsuffixes,

-   getbestsuffix = getbestsuffix,

-   writevars     = writevars,

-   wordwrap      = wordwrap,

-   new_package   = new_package,

+   ref            = ref,

+   expand         = expand,

+   read           = read,

+   readbool       = readbool,

+   readlines      = readlines,

+   hasflag        = hasflag,

+   readflag       = readflag,

+   wordwrap       = wordwrap,

+   echo           = echo,

+   echovars       = echovars,

+   warning        = warning,

+   error          = err,

+   mread          = mread,

+   set            = set,

+   unset          = unset,

+   safeset        = safeset,

+   mset           = mset,

+   inherit_from   = inherit_from,

+   inherit        = inherit,

+   alias          = alias,

+   bialias        = bialias,

+   zalias         = zalias,

+   set_current    = set_current,

+   set_verbose    = set_verbose,

+   qualify        = qualify,

+   mergelists     = mergelists,

+   suffixes       = suffixes,

+   all_suffixes   = all_suffixes,

+   suffix         = suffix,

+   source_suffix  = source_suffix,

+   patch_suffix   = patch_suffix,

+   writevars      = writevars,

+   -- deprecated aliases

+   explicitset    = set,

+   explicitunset  = unset,

+   getsuffixes    = suffixes,

+   setcurrent     = setcurrent,

+   getsuffixed    = getsuffixed,

+   getbestsuffix  = getbestsuffix,

  }

file added
+2
@@ -0,0 +1,2 @@ 

+ auto_install  doc_install

+ auto_files    doc_files

file added
+27
@@ -0,0 +1,27 @@ 

+ local fedora = require "fedora.common"

+ local   auto = require "fedora.auto"

+ local    doc = require "fedora.srpm.doc"

+ 

+ local namespace = "doc"

+ 

+ local function install(suffix, verbose)

+  auto.id(namespace .. '.install("' .. suffix .. '")')

+   doc.env(suffix, verbose)

+   print(rpm.expand([[

+ %__]] .. namespace .. [[_install

+ ]]))

+ end

+ 

+ local function files(suffix, verbose)

+  auto.id(namespace .. '.files("' .. suffix .. '")')

+   doc.env(suffix, verbose)

+   print(rpm.expand([[

+ %__]] .. namespace .. [[_files

+ ]]))

+ end

+ 

+ return {

+   floop   = doc.floop,

+   install = install,

+   files   = files,

+ }

file added
+2
@@ -0,0 +1,2 @@ 

+ auto_init  doc_init

+ auto_pkg   doc_pkg

file added
+103
@@ -0,0 +1,103 @@ 

+ 

+ local fedora = require "fedora.common"

+ local   auto = require "fedora.auto"

+ local buildsys = require "fedora.srpm.buildsys"

+ 

+ local namespace = "doc"

+ 

+ local rads = {

+   ["key"]      = {"docs"},

+   ["read"]     = {"epoch", "version", "license", "licenses",

+                   "licenses_exclude", "docs", "docs_exclude", "tags"},

+   ["computed"] = {"name", "summary", "requires", "description", "list"}

+ }

+ 

+ local function suffixes()

+   return fedora.all_suffixes(fedora.qualify(rads["key"], namespace))

+ end

+ 

+ local function floop(f, index, otherargs, verbose)

+   local range = suffixes(verbose)

+   auto.floop(f, index, range, otherargs)

+ end

+ 

+ local function radicals()

+   local R = {}

+   for k, v in pairs(rads) do

+     R[k] = v

+   end

+   local all = {}

+   for k, v in pairs(R) do

+     all = fedora.mergelists({all, v})

+   end

+   R["all"] = all

+   return R

+ end

+ 

+ local function init(suffix, verbose, informative)

+  auto.id(namespace .. '.init("' .. suffix .. '")')

+   local ismain = (suffix == "") or (suffix == "0")

+   local myrads = radicals()

+   local requires = [[%{lua:

+     local fedora = require "fedora.common"

+     local namespace = "]] .. namespace .. [["

+     local suffix    = "]] .. suffix .. [["

+     local requires = {}

+     local pkgs = fedora.read(namespace .. "_packages" .. suffix)

+     if pkgs then

+       for pkg in pkgs:gmatch("[^%s,;]+") do

+         table.insert(requires, "Enhances:       " .. pkg)

+       end

+     else

+       for _, z in ipairs(fedora.suffixes("buildsys_name")) do

+         if not fedora.read("buildsys_name" .. z):match("-doc$") then

+           table.insert(requires,

+             "Enhances:       %{buildsys_name" .. z .. "}")

+         end

+       end

+     end

+     print(table.concat(requires, "\\n"))

+     }]]

+   fedora.mset(myrads["all"], namespace, suffix, {{

+       ["name"]        = "%{source_name}-doc",

+       ["summary"]     = "Optional documentation files %{source_name}",

+       ["requires"]    = requires,

+       ["description"] = [[

+ %{?source_description}

+ 

+ This package provides optional documentation files shipped with %{source_name}.

+ ]],

+       ["list"] = "%{_builddir}/%{?buildsubdir}/" ..

+                  "%{" .. namespace .. "_name" .. suffix .. "}.lst",

+     }}, verbose, false)

+   if informative then

+     fedora.echo("Variables read or set by %%" .. namespace .. "_init")

+     fedora.echovars(fedora.qualify(myrads["all"], namespace), suffix)

+   end

+ end

+ 

+ local function env(suffix, verbose)

+   local myrads = radicals(suffix, verbose)

+   fedora.set_current(fedora.qualify(myrads["all"], namespace), suffix, verbose)

+ end

+ 

+ local function pkg(suffix, verbose)

+   auto.id(namespace .. '.pkg("' .. suffix .. '")')

+   fedora.mset(buildsys.radicals, "__current_buildsys", "", {

+       { ["tags"] = [[

+ BuildArch:      noarch

+ %{?]] .. namespace .. [[_requires]] .. suffix .. [[}

+ %{?]] .. namespace .. [[_tags]]     .. suffix .. [[}]] },

+       fedora.mread({"name", "summary", "epoch", "version", "license",

+                     "description"}, namespace, suffix, false),

+     }, verbose, true)

+   buildsys.pkg(verbose)

+ end

+ 

+ return {

+   suffixes = suffixes,

+   floop    = floop,

+   init     = init,

+   env      = env,

+   pkg      = pkg,

+ }

file added
+1
@@ -0,0 +1,1 @@ 

+ auto_prep forge_prep

file added
+44
@@ -0,0 +1,44 @@ 

+ -- Lua code used by macros.forge and derivatives; RPM stage

+ 

+ local fedora = require "fedora.common"

+ local   auto = require "fedora.auto"

+ local  forge = require "fedora.srpm.forge"

+ 

+ -- This is constrained by the design of %apply_patch

+ local function apply_patches(suffix, verbose)

+   local quiet = ""

+   if not verbose then

+     quiet = "-q "

+   end

+   for _, patch in ipairs(fedora.readlines("forge_patchlist" .. suffix)) do

+     local ps = fedora.patch_suffix(patch)

+     if ps then

+       for _, o in ipairs({"patch_mode", "patch_level"}) do

+         fedora.safeset(o .. ps, "%{forge_" .. o .. suffix .. "}")

+       end

+       -- %apply_patch does not have the notion of a disposable local currentfoo-like

+       -- control variable, you need to override the main one

+       fedora.set("__scm", "%{patch_mode" .. ps .. "}")

+       -- %apply_patch requires specifying the patch to apply in 3 different forms!

+       -- As a filename, as a full path, and as a patch suffix

+       print(rpm.expand(

+         "%apply_patch " ..  quiet .. "%{patch_level" .. ps .. "} " ..

+         "-m " .. patch .. " %{_sourcedir}/" .. patch .. " " .. ps .. "\n"))

+     end

+   end

+ end

+ 

+ -- %forge_prep core

+ local function prep(suffix, verbose)

+   auto.id('forge.prep("' .. suffix .. '")')

+   forge.env(suffix, verbose)

+   print(rpm.expand([[

+ %setup %{?__current_quiet} %{__current_forge_setup_arguments}

+ ]]))

+   apply_patches(suffix, verbose)

+ end

+ 

+ return {

+   floop     = forge.floop,

+   prep      = prep,

+ }

file added
+3
@@ -0,0 +1,3 @@ 

+ auto_init    forge_init

+ auto_sources forge_sources

+ auto_patches forge_patches

file added
+438
@@ -0,0 +1,438 @@ 

+ -- Lua code used by macros.forge-srpm and derivatives

+ 

+ local fedora = require "fedora.common"

+ local   auto = require "fedora.auto"

+ 

+ -- Computes the suffix of a version string, removing vprefix if it matches

+ -- For example with vprefix 1.2.3: 1.2.3.rc2 → .rc2 but 1.2.30 → 1.2.30 not 0

+ local function version_suffix(vstring, vprefix)

+   if (vstring:sub(1, #vprefix) == vprefix) and

+      (not string.match(vstring:sub(#vprefix + 1), "^%.?%d")) then

+     return vstring:sub(#vprefix + 1)

+   else

+     return vstring

+   end

+ end

+ 

+ -- Check if an identified url is sane

+ local function check_forge_url(url, id, silent)

+   local checkedurl  = nil

+   local checkedid   = nil

+   local urlpatterns = {

+     ["gitlab"] = {

+       ["pattern"]     = 'https://[^/]+/[^/]+/[^/#?]+',

+       ["description"] = 'https://(…[-.])gitlab[-.]…/owner/repo'},

+     ["pagure"] = {

+       ["pattern"]     = 'https://[^/]+/[^/#?]+',

+       ["description"] = 'https://pagure.io/repo'},

+     ["pagure_ns"] = {

+       ["pattern"]     = 'https://[^/]+/[^/]+/[^/#?]+',

+       ["description"] = 'https://pagure.io/namespace/repo'},

+     ["pagure_fork"] = {

+       ["pattern"]     = 'https://[^/]+/fork/[^/]+/[^/#?]+',

+       ["description"] = 'https://pagure.io/fork/owner/repo'},

+     ["pagure_ns_fork"] = {

+       ["pattern"]     = 'https://[^/]+/fork/[^/]+/[^/]+/[^/#?]+',

+       ["description"] = 'https://pagure.io/fork/owner/namespace/repo'},

+     ["gitea.com"] = {

+       ["pattern"]     = 'https://[^/]+/[^/]+/[^/#?]+',

+       ["description"] = 'https://gitea.com/owner/repo'},

+     ["github"] = {

+       ["pattern"]     = 'https://[^/]+/[^/]+/[^/#?]+',

+       ["description"] = 'https://(…[-.])github[-.]…/owner/repo'},

+     ["code.googlesource.com"] = {

+       ["pattern"]     = 'https://code.googlesource.com/[^#?]*[^/#?]+',

+       ["description"] = 'https://code.googlesource.com/…/repo'},

+     ["bitbucket.org"] = {

+       ["pattern"]     = 'https://[^/]+/[^/]+/[^/#?]+',

+       ["description"] = 'https://bitbucket.org/owner/repo'}}

+   if (urlpatterns[id] ~= nil) then

+     checkedurl = url:match(urlpatterns[id]["pattern"])

+     if (checkedurl == nil) then

+       if not silent then

+         fedora.error(id .. " URLs must match " ..

+                      urlpatterns[id]["description"] .. "!")

+       end

+     else

+       checkedid = id

+     end

+   end

+   return checkedurl, checkedid

+ end

+ 

+ -- Check if an url matches a known forge

+ local function idforge(url, silent)

+   local forge_url = nil

+   local forge    = nil

+   if (url ~= "") then

+     forge = url:match("^[^:]+://([^/]+)/")

+     if not forge then

+       if not silent then

+         fedora.error([[

+ URLs must include a protocol such as https:// and a path starting with /. Read:

+ ]] .. url)

+       end

+     else

+       if (forge == "pagure.io") then

+         if     url:match("[^:]+://pagure.io/fork/[^/]+/[^/]+/[^/]+") then

+           forge = "pagure_ns_fork"

+         elseif url:match("[^:]+://pagure.io/fork/[^/]+/[^/]+") then

+           forge = "pagure_fork"

+         elseif url:match("[^:]+://pagure.io/[^/]+/[^/]+") then

+           forge = "pagure_ns"

+         elseif url:match("[^:]+://pagure.io/[^/]+") then

+           forge = "pagure"

+         end

+       elseif forge:match("^gitlab[%.-]") or forge:match("[%.-]gitlab[%.]") then

+         forge = "gitlab"

+       elseif forge:match("^github[%.-]") or forge:match("[%.-]github[%.]") then

+         forge = "github"

+       end

+       forge_url, forge = check_forge_url(url, forge, silent)

+     end

+   end

+   return forge_url, forge

+ end

+ 

+ -- rpm variable radicals

+ local rads = {

+   -- Elements that will be pushed SRPM-level

+   ["source"] = {"version", "url"},

+   ["forge"]  = {

+     -- key variables that may control a declaration block

+     ["key"]      = {"url"},

+     -- variables, used to compute a SCM reference

+     ["ref"]      = {"tag", "commit", "branch", "version", "ref"},

+     -- other computed or recomputed variables

+     ["computed"] = {"tag", "source", "source_suffix", "setup_arguments",

+                     "patch_mode", "patch_level", "extract_dir",

+                     "time", "date", "file_ref"},},

+ }

+ 

+ -- Return all the suffixes for which one of the forge key variables is set

+ local function suffixes()

+   return fedora.all_suffixes(fedora.qualify(rads["forge"]["key"], "forge"))

+ end

+ 

+ -- Executes the f function in a loop

+ --   – the loop is controled by index (a string):

+ --     — if the index value given to floop is nil, loop over the whole

+ --       range. Use readflag() to process rpm flag arguments safely.

+ --     — otherwise execute f for the specified index value only.

+ --   – f is passed index then otherargs as arguments.

+ --   – the index range is controlled by suffixes()

+ local function floop(f, index, otherargs)

+   auto.floop(f, index, suffixes(), otherargs)

+ end

+ 

+ -- forge variable derivation patterns

+ local function rules(suffix)

+   local R = {

+   ["default"] = {

+     ["scm"]           = "git",

+     ["archive_extension"]  = "tar.bz2",

+     ["repo"]          = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("^[^:]+://[^/]+/[^/]+/([^/?#]+)"))}',

+     ["archive_name"]  = "%{forge_repo"         .. suffix .. "}-%{forge_ref" .. suffix .. "}",

+     ["top_dir"]       = "%{forge_archive_name" .. suffix .. "}" },

+   ["gitlab"] = {

+     ["source"]  = "%{forge_url" .. suffix .. "}/-/archive/%{forge_ref" .. suffix .. "}/%{forge_archive_name" .. suffix .. "}.%{forge_archive_extension" .. suffix .. "}" },

+   ["pagure"] = {

+     ["archive_extension"]  = "tar.gz",

+     ["repo"]          = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("^[^:]+://[^/]+/([^/?#]+)"))}',

+     ["source"]        = "%{forge_url" .. suffix ..   "}/archive/%{forge_ref" .. suffix .. "}/%{forge_archive_name" .. suffix .. "}.%{forge_archive_extension" .. suffix .. "}" },

+   ["pagure_ns"] = {

+     ["archive_extension"]  = "tar.gz",

+     ["namespace"]    = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("^[^:]+://[^/]+/([^/]+)/[^/?#]+"))}',

+     ["repo"]         = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("^[^:]+://[^/]+/[^/]+/([^/?#]+)"))}',

+     ["archive_name"] = "%{forge_namespace" .. suffix .. "}-%{forge_repo" .. suffix .. "}-%{forge_ref" .. suffix .. "}",

+     ["source"]       = "%{forge_url" .. suffix .. "}/archive/%{forge_ref"   .. suffix .. "}/%{forge_archive_name" .. suffix .. "}.%{forge_archive_extension" .. suffix .. "}" },

+   ["pagure_fork"] = {

+     ["archive_extension"]  = "tar.gz",

+     ["owner"]        = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("https://[^/]+/fork/([^/]+)/[^/?#]+"))}',

+     ["repo"]         = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("https://[^/]+/fork/[^/]+/([^/?#]+)"))}',

+     ["archive_name"] = "%{forge_owner" .. suffix .. "}-%{forge_repo" .. suffix .. "}-%{forge_ref" .. suffix .. "}",

+     ["source"]  = "%{forge_url" .. suffix .. "}/archive/%{forge_ref" .. suffix .. "}/%{forge_archive_name" .. suffix .. "}.%{forge_archive_extension" .. suffix .. "}" },

+   ["pagure_ns_fork"] = {

+     ["owner"]        = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("https://[^/]+/fork/([^/]+)/[^/]+/[^/?#]+"))}',

+     ["namespace"]    = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("https://[^/]+/fork/[^/]+/([^/]+)/[^/?#]+")}',

+     ["repo"]         = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("https://[^/]+/fork/[^/]+/[^/]+/([^/?#]+)")}',

+     ["archive_name"] = "%{forge_owner" .. suffix .. "}-%{forge_namespace" .. suffix .. "}-%{forge_repo" .. suffix .. "}-%{forge_ref" .. suffix .. "}",

+     ["source"]       = "%{forge_url" .. suffix .. "}/archive/%{forge_ref" .. suffix .. "}/%{forge_archive_name" .. suffix .. "}.%{forge_archive_extension" .. suffix .. "}" },

+   ["gitea.com"]  = {

+     ["archive_extension"]  = "tar.gz",

+     ["archive_name"]       = "%{forge_file_ref" .. suffix .. "}",

+     ["source"]             = "%{forge_url" .. suffix .. "}/archive/%{forge_ref" .. suffix .. "}.%{forge_archive_extension" .. suffix .. "}",

+     ["top_dir"]            = "%{forge_repo}" },

+   ["github"] = {

+     ["archive_extension"]  = "tar.gz",

+     ["archive_name"]       = "%{forge_repo" .. suffix .. "}-%{forge_file_ref" .. suffix .. "}",

+     ["source"]             = "%{forge_url"  .. suffix .. "}/archive/%{forge_ref" .. suffix .. "}/%{forge_archive_name" .. suffix .. "}.%{forge_archive_extension" .. suffix .. "}" },

+   ["code.googlesource.com"] = {

+     ["archive_extension"]  = "tar.gz",

+     ["repo"]               = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("^[^:]+://.+/([^/?#]+)"))}',

+     ["source"]             = "%{forge_url" .. suffix .. "}/+archive/%{forge_ref" .. suffix .. "}.%{forge_archive_extension"  .. suffix .. "}",

+     ["top_dir"]            = "" },

+   ["bitbucket.org"] = {

+     ["short_commit"] = '%{lua:print(string.sub(rpm.expand("%{forge_commit' .. suffix .. '}"), 1, 12))}',

+     ["owner"]        = '%{lua:print(rpm.expand("%{forge_url' .. suffix .. '}"):match("^[^:]+://[^/]+/([^/?#]+)"))}',

+     ["archive_name"] = "%{forge_owner" .. suffix .. "}-%{forge_repo" .. suffix .. "}-%{forge_short_commit" .. suffix .. "}",

+     ["source"]       = "%{forge_url" .. suffix .. "}/get/%{forge_ref" .. suffix .. "}.%{forge_archive_extension"  .. suffix .. "}" } }

+   return R

+ end

+ 

+ -- Returns forge radicals

+ local function radicals(suffix)

+   local R = {}

+   for k, v in pairs(rads["forge"]) do

+     R[k] = v

+   end

+   R["rules"] = {}

+   for _, v in pairs(rules(suffix)) do

+     local l = {}

+     for k, _ in pairs(v) do

+       table.insert(l, k)

+     end

+     R["rules"] = fedora.mergelists({R["rules"], l})

+   end

+   R["computed"] = fedora.mergelists({R["computed"], R["key"], R["rules"]})