From 05f3c3f3a633440afd9285e0ba173f7556dc5809 Mon Sep 17 00:00:00 2001 From: Tobias Ulmer Date: Tue, 24 Jun 2014 19:23:56 +0200 Subject: [PATCH] Add source module and refactor e2source config handling. * Introduce a source module with a basic_source class, which all source plugins have to implement. * Move info.sources to source module. * Turn e2source config tables into defined objects with clear interface. * Convert all source plugins to class interface, cleaning source validation up. * Improve error checking and detect typos in source configs. * Add a stringlist module providing a ADT for the common case of dealing with arrays of strings. * Fix files plugin calculation of sourceid when using per-file licences entries * Many small fixes... Signed-off-by: Tobias Ulmer --- generic/e2lib.lua | 8 +- local/Makefile | 3 +- local/e2-fetch-sources.lua | 69 +++--- local/e2-ls-project.lua | 20 +- local/e2tool.lua | 148 ++---------- local/scm.lua | 139 ++---------- local/sl.lua | 276 +++++++++++++++++++++++ local/source.lua | 433 +++++++++++++++++++++++++++++++++++ plugins/cvs.lua | 372 ++++++++++++++++++------------ plugins/files.lua | 409 ++++++++++++++++++++++----------- plugins/git.lua | 417 +++++++++++++++++++++------------- plugins/svn.lua | 448 ++++++++++++++++++++++--------------- 12 files changed, 1814 insertions(+), 928 deletions(-) create mode 100644 local/sl.lua create mode 100644 local/source.lua diff --git a/generic/e2lib.lua b/generic/e2lib.lua index a66b868..7e51d20 100644 --- a/generic/e2lib.lua +++ b/generic/e2lib.lua @@ -2458,11 +2458,11 @@ function e2lib.vrfy_dict_exp_keys(t, name, ekeyvec) for k,_ in pairs(t) do if not lookup[k] then if not e then - e = err.new("table %s contains unexpected key %q", - name, tostring(k)) + e = err.new("unexpected key %q in %s", + tostring(k), name) else - e:append("table %s contains unexpected key %q", - name, tostring(k)) + e = err.new("unexpected key %q in %s", + tostring(k), name) end end end diff --git a/local/Makefile b/local/Makefile index 7da1dd6..cd0363c 100644 --- a/local/Makefile +++ b/local/Makefile @@ -41,7 +41,8 @@ LOCALLUATOOLS = e2-build e2-dlist e2-dsort e2-fetch-sources \ e2-build-numbers e2-cf e2-help LOCALLUALIBS= digest.lua e2build.lua e2tool.lua environment.lua \ - policy.lua scm.lua licence.lua chroot.lua project.lua + policy.lua scm.lua licence.lua chroot.lua project.lua \ + source.lua sl.lua LOCALTOOLS = $(LOCALLUATOOLS) .PHONY: all install uninstall local install-local doc install-doc diff --git a/local/e2-fetch-sources.lua b/local/e2-fetch-sources.lua index 9491891..ee52711 100644 --- a/local/e2-fetch-sources.lua +++ b/local/e2-fetch-sources.lua @@ -37,6 +37,7 @@ local e2tool = require("e2tool") local err = require("err") local scm = require("scm") local chroot = require("chroot") +local source = require("source") local function e2_fetch_source(arg) local rc, re = e2lib.init() @@ -138,16 +139,16 @@ local function e2_fetch_source(arg) local e = err.new() -- no message yet, append the summary later on -- fetch - for _, s in pairs(info.sources) do - local has_wc = scm.has_working_copy(info, s.name) - local wc_avail = scm.working_copy_available(info, s.name) - if opts.fetch and sel[s.name] then + for sourcename, _ in pairs(source.sources) do + local has_wc = scm.has_working_copy(info, sourcename) + local wc_avail = scm.working_copy_available(info, sourcename) + if opts.fetch and sel[sourcename] then if wc_avail then e2lib.logf(1, - "working copy for %s is already available", s.name) + "working copy for %s is already available", sourcename) else - e2lib.logf(1, "fetching working copy for source %s", s.name) - local rc, re = scm.fetch_source(info, s.name) + e2lib.logf(1, "fetching working copy for source %s", sourcename) + local rc, re = scm.fetch_source(info, sourcename) if not rc then e:cat(re) end @@ -156,15 +157,15 @@ local function e2_fetch_source(arg) end -- update - for _, s in pairs(info.sources) do - local has_wc = scm.has_working_copy(info, s.name) - local wc_avail = scm.working_copy_available(info, s.name) - if opts.update and has_wc and sel[s.name] then + for sourcename, _ in pairs(source.sources) do + local has_wc = scm.has_working_copy(info, sourcename) + local wc_avail = scm.working_copy_available(info, sourcename) + if opts.update and has_wc and sel[sourcename] then if not wc_avail then - e2lib.logf(1, "working copy for %s is not available", s.name) + e2lib.logf(1, "working copy for %s is not available", sourcename) else - e2lib.logf(1, "updating working copy for %s", s.name) - local rc, re = scm.update(info, s.name) + e2lib.logf(1, "updating working copy for %s", sourcename) + local rc, re = scm.update(info, sourcename) if not rc then e:cat(re) end @@ -182,42 +183,42 @@ local function e2_fetch_source(arg) local sel = {} -- selected sources if #arguments > 0 then - for _, x in pairs(arguments) do - if info.sources[x] and not opts.result then - e2lib.logf(3, "is regarded as source: %s", x) - sel[x] = x - elseif info.results[x] and opts.result then - e2lib.logf(3, "is regarded as result: %s", x) - local res = info.results[x] - for _, s in ipairs(res.sources) do - sel[s] = s + for _, srcresname in pairs(arguments) do + if source.sources[srcresname] and not opts.result then + e2lib.logf(3, "is regarded as source: %s", srcresname) + sel[srcresname] = true + elseif info.results[srcresname] and opts.result then + e2lib.logf(3, "is regarded as result: %s", srcresname) + local res = info.results[srcresname] + for _, sourcename in ipairs(res.sources) do + sel[sourcename] = true end elseif opts.result then - error(err.new("is not a result: %s", x)) + error(err.new("is not a result: %s", srcresname)) else - error(err.new("is not a source: %s", x)) + error(err.new("is not a source: %s", srcresname)) end end elseif opts["all"] then -- select all sources - for s,src in pairs(info.sources) do - sel[s] = s + for sourcename, _ in pairs(source.sources) do + sel[sourcename] = true end end -- select all sources by scm type - for s, src in pairs(info.sources) do - if select_type[src.type] then - sel[s] = s + for sourcename, src in pairs(source.sources) do + if select_type[src:get_type()] then + sel[sourcename] = true end end - for _, s in pairs(sel) do - e2lib.logf(2, "selecting source: %s" , s) - local src = info.sources[s] + for sourcename, _ in pairs(sel) do + e2lib.logf(2, "selecting source: %s" , sourcename) + local src = source.sources[sourcename] if not src then - e:append("selecting invalid source: %s", s) + e:append("selecting invalid source: %s", sourcename) end end if e:getcount() > 0 then diff --git a/local/e2-ls-project.lua b/local/e2-ls-project.lua index 195f8dc..eb822a3 100644 --- a/local/e2-ls-project.lua +++ b/local/e2-ls-project.lua @@ -41,6 +41,7 @@ local policy = require("policy") local scm = require("scm") local chroot = require("chroot") local project = require("project") +local source = require("source") local function e2_ls_project(arg) local rc, re = e2lib.init() @@ -98,16 +99,16 @@ local function e2_ls_project(arg) local sources = {} if opts.all then - for s, _ in pairs(info.sources) do - table.insert(sources, s) + for sourcename, _ in pairs(source.sources) do + table.insert(sources, sourcename) end else local yet = {} for _, r in pairs(results) do - for _, s in ipairs(info.results[r].sources) do - if not yet[s] then - table.insert(sources, s) - yet[s] = true + for _, sourcename in ipairs(info.results[r].sources) do + if not yet[sourcename] then + table.insert(sources, sourcename) + yet[sourcename] = true end end end @@ -241,16 +242,15 @@ local function e2_ls_project(arg) local s2 = " " p1(s1, s2, "src") local len = #sources - for _, s in pairs(sources) do - local src = info.sources[s] + for _, sourcename in pairs(sources) do len = len - 1 if len == 0 then s2 = " " else s2 = "|" end - p2(s1, s2, src.name) - local t, re = scm.display(info, src.name) + p2(s1, s2, sourcename) + local t, re = source.sources[sourcename]:display() if not t then error(re) end diff --git a/local/e2tool.lua b/local/e2tool.lua index 2726763..96a4c5e 100644 --- a/local/e2tool.lua +++ b/local/e2tool.lua @@ -51,6 +51,7 @@ local transport = require("transport") local url = require("url") local chroot = require("chroot") local project = require("project") +local source = require("source") -- Build function table, see end of file for details. local e2tool_ftab = {} @@ -296,9 +297,9 @@ local function check_result(info, resultname) e:append("source attribute:") e:cat(re) else - for i,s in ipairs(res.sources) do - if not info.sources[s] then - e:append("source does not exist: %s", s) + for _,sourcename in ipairs(res.sources) do + if not source.sources[sourcename] then + e:append("source does not exist: %s", sourcename) end end end @@ -707,68 +708,6 @@ function e2tool.src_res_path_to_name(pathname) return pathname:gsub("/", ".") end ---- Load all source configs. Creates and populates the info.sources dictionary. --- @param info Info table. --- @return True on success, false on error. --- @return Error object on failure. -local function load_source_configs(info) - local rc, re, e - local sources, list, path, types - - e = err.new("error loading source configuration") - info.sources = {} - sources, re = gather_source_paths(info) - if not sources then - return false, e:cat(re) - end - - for _,src in ipairs(sources) do - path = e2tool.sourceconfig(src, info.root) - types = { "e2source", } - rc, re = e2tool.verify_src_res_pathname_valid_chars(src) - if not rc then - e:append("invalid source file name: %s", src) - e:cat(re) - return false, e - end - - list, re = load_user_config2(info, path, types) - if not list then - return false, e:cat(re) - end - - local name - for _,item in ipairs(list) do - name = item.data.name - item.data.directory = src - if not name and #list == 1 then - e2lib.warnf("WDEFAULT", "`name' attribute missing in source config.") - e2lib.warnf("WDEFAULT", " Defaulting to directory name") - item.data.name = e2tool.src_res_path_to_name(src) - name = item.data.name - end - - if not name then - return false, e:append("`name' attribute missing in source config") - end - - rc, re = e2tool.verify_src_res_name_valid_chars(name) - if not rc then - e:append("invalid source name: %s", name) - e:cat(re) - return false, e - end - - if info.sources[name] then - return false, e:append("duplicate source: %s", name) - end - - info.sources[name] = item.data - end - end - return true -end - --- Get project-relative directory for a result. -- Returns the relative path to the resultdir and optionally a name and prefix -- (e.g. prefix/res/name). @@ -933,43 +872,6 @@ local function load_result_configs(info) return true end ---- check source. -local function check_source(info, sourcename) - local src = info.sources[sourcename] - local rc, e, re - if not src then - e = err.new("no source by that name: %s", sourcename) - return false, e - end - e = err.new("in source: %s", sourcename) - if not src.type then - e2lib.warnf("WDEFAULT", "in source %s", sourcename) - e2lib.warnf("WDEFAULT", " type attribute defaults to `files'") - src.type = "files" - end - rc, re = scm.validate_source(info, sourcename) - if not rc then - return false, re - end - return true -end - ---- check sources. -local function check_sources(info) - local rc, re - local e = err.new("Error while checking sources") - for n,s in pairs(info.sources) do - rc, re = check_source(info, n) - if not rc then - e:cat(re) - end - end - if e:getcount() > 1 then - return false, e - end - return true -end - --- Checks project information for consistancy. -- @param info Info table. -- @return True on success, false on error. @@ -977,10 +879,7 @@ end local function check_project_info(info) local rc, re, e e = err.new("error in project configuration") - rc, re = check_sources(info) - if not rc then - return false, e:cat(re) - end + rc, re = check_results(info) if not rc then return false, e:cat(re) @@ -1110,8 +1009,6 @@ function e2tool.collect_project_info(info, skip_load_config) end end - info.sources = {} - -- read environment configuration info.env = {} -- global and result specfic env (deprecated) info.env_files = {} -- a list of environment files @@ -1141,7 +1038,7 @@ function e2tool.collect_project_info(info, skip_load_config) end -- sources - rc, re = load_source_configs(info) + rc, re = source.load_source_configs(info) if not rc then return false, e:cat(re) end @@ -1209,8 +1106,6 @@ function e2tool.collect_project_info(info, skip_load_config) end end - --e2tool.add_source_results(info) - -- provide a sorted list of results info.results_sorted = {} for r,res in pairs(info.results) do @@ -1218,13 +1113,6 @@ function e2tool.collect_project_info(info, skip_load_config) end table.sort(info.results_sorted) - -- provided sorted list of sources - info.sources_sorted = {} - for s,src in pairs(info.sources) do - table.insert(info.sources_sorted, s) - end - table.sort(info.sources_sorted) - rc, re = policy.init(info) if not rc then return false, e:cat(re) @@ -1645,14 +1533,17 @@ function e2tool.pbuildid(info, resultname) hash.hash_line(hc, r.name) - for _,s in ipairs(r.sources) do - local src = info.sources[s] - local source_set = r.build_mode.source_set() - local rc, re, sourceid = scm.sourceid(info, s, source_set) - if not rc then + for _,sourcename in ipairs(r.sources) do + local src, sourceid, sourceset + + src = source.sources[sourcename] + sourceset = r.build_mode.source_set() + sourceid, re = src:sourceid(sourceset) + if not sourceid then return false, e:cat(re) end - hash.hash_line(hc, s) -- source name + + hash.hash_line(hc, sourcename) -- source name hash.hash_line(hc, sourceid) -- sourceid end for _,d in ipairs(r.depends) do @@ -1728,11 +1619,16 @@ end -- @param resultname string: name of a result -- @return table: environment variables valid for the result function e2tool.env_by_result(info, resultname) + assert(type(info) == "table") + assert(type(resultname) == "string" and #resultname > 0) + + local src local res = info.results[resultname] local env = environment.new() env:merge(info.global_env, false) - for _, s in ipairs(res.sources) do - env:merge(info.sources[s]._env, true) + for _, sourcename in ipairs(res.sources) do + src = source.sources[sourcename] + env:merge(src:get_env(), true) end env:merge(res._env, true) return env diff --git a/local/scm.lua b/local/scm.lua index cb38345..a17da01 100644 --- a/local/scm.lua +++ b/local/scm.lua @@ -29,11 +29,10 @@ ]] local scm = {} -local e2lib = require("e2lib") -local environment = require("environment") +package.loaded["scm"] = scm local err = require("err") -local licence = require("licence") local strict = require("strict") +local source = require("source") -- scm modules local scms = {} @@ -87,16 +86,21 @@ function scm.register_interface(name) end local function func(info, sourcename, ...) - local src = info.sources[sourcename] + assert(info) + assert(sourcename) + + local typ local rc, re, e + e = err.new("calling scm operation failed") - if not scms[src.type] then - return false, e:append("no such source type: %s", src.type) + + typ = source.sources[sourcename]:get_type() + if not scms[typ] then + return false, e:append("no such source type: %s", tostring(typ)) end - local f = scms[src.type][name] + local f = scms[typ][name] if not f then - e:append("%s() is not implemented for source type: %s", - name, src.type) + e:append("%s() is not implemented for source type: %s", name, typ) return false, e end return f(info, sourcename, ...) @@ -140,120 +144,6 @@ function scm.register_function(scmtype, name, func) return true end ---- apply default values where possible and a source configuration is --- incomplete --- @param info the info table --- @param sourcename the source name --- @return bool --- @return an error object on failure -local function source_apply_default_licences(info, sourcename) - local e = err.new("applying default licences failed.") - local src = info.sources[ sourcename ] - - if not src.licences and src.licence then - e2lib.warnf("WDEPRECATED", "in source %s:", src.name) - e2lib.warnf("WDEPRECATED", - " licence attribute is deprecated. Replace by licences.") - src.licences = src.licence - end - if src.licences == nil then - e2lib.warnf("WDEFAULT", "in source %s:", src.name) - e2lib.warnf("WDEFAULT", - " licences attribute missing. Defaulting to empty list.") - src.licences = {} - elseif type(src.licences) == "string" then - e2lib.warnf("WDEPRECATED", "in source %s:", src.name) - e2lib.warnf("WDEPRECATED", - " licences attribute is not in table format. Converting.") - src.licences = { src.licences } - end - - if type(src.licences) ~= "table" then - e:append("licences attribute is of invalid type") - return false, e - end - - for i, s in pairs(src.licences) do - if type(i) ~= "number" or type(s) ~= "string" then - e:append("licences attribute is not a list of strings") - return false, e - end - end - for _,l in ipairs(src.licences) do - if not licence.licences[l] then - e:append("unknown licence: %s", l) - return false, e - end - end - return true -end - ---- validate generic source configuration, usable by SCM plugins --- @param info the info table --- @param sourcename the source name --- @return bool --- @return an error object on failure -function scm.generic_source_validate(info, sourcename) - local src = info.sources[sourcename] - local rc, re - local e - if not src then - return false, err.new("invalid source: %s", sourcename) - end - e = err.new("in source %s:", sourcename) - rc, re = source_apply_default_licences(info, sourcename) - if not rc then - return false, e:cat(re) - end - if not src.type then - e:append("source has no `type' attribute") - end - if src.env and type(src.env) ~= "table" then - e:append("source has invalid `env' attribute") - else - if not src.env then - e2lib.warnf("WDEFAULT", - "source has no `env' attribute. Defaulting to empty dictionary") - src.env = {} - end - src._env = environment.new() - for k,v in pairs(src.env) do - if type(k) ~= "string" then - e:append("in `env' dictionary: key is not a string: %s", tostring(k)) - elseif type(v) ~= "string" then - e:append("in `env' dictionary: value is not a string: %s", tostring(v)) - else - src._env:set(k, v) - end - end - end - if e:getcount() > 1 then - return false, e - end - return true, nil -end - ---- apply default values where possible --- @param info the info table --- @param sourcename the source name --- @return bool --- @return an error object on failure -function scm.generic_source_default_working(info, sourcename) - local src - - src = info.sources[sourcename] - - if not src.working then - src.working = e2lib.join("in", sourcename) - - e2lib.warnf("WDEFAULT", "in source %s:", sourcename) - e2lib.warnf("WDEFAULT", " `working' attribute defaults to '%s'.", - src.working) - end - - return true -end - --- do some consistency checks required before using sources -- @param info -- @param sourcename string: source name @@ -274,15 +164,12 @@ function scm.generic_source_check(info, sourcename, require_workingcopy) return true, nil end -scm.register_interface("sourceid") -scm.register_interface("validate_source") scm.register_interface("toresult") scm.register_interface("prepare_source") scm.register_interface("fetch_source") scm.register_interface("update") scm.register_interface("check_workingcopy") scm.register_interface("working_copy_available") -scm.register_interface("display") scm.register_interface("has_working_copy") return strict.lock(scm) diff --git a/local/sl.lua b/local/sl.lua new file mode 100644 index 0000000..cc9b87e --- /dev/null +++ b/local/sl.lua @@ -0,0 +1,276 @@ +--- Universal string list. Handy for storing result-, licence-, source names. +-- @module local.sl + +-- Copyright (C) 2014 emlix GmbH, see file AUTHORS +-- +-- This file is part of e2factory, the emlix embedded build system. +-- For more information see http://www.e2factory.org +-- +-- e2factory is a registered trademark of emlix GmbH. +-- +-- e2factory is free software: you can redistribute it and/or modify it under +-- the terms of the GNU General Public License as published by the +-- Free Software Foundation, either version 3 of the License, or (at your +-- option) any later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +-- more details. + +local sl = {} +local class = require("class") +local err = require("err") +local e2lib = require("e2lib") +local strict = require("strict") + +-- ---------------------------------------------------------------------------- +-- There is plenty of optimization potential here, however the string lists +-- are usually very small: 0 - 100 entries. Don't waste time. +-- ---------------------------------------------------------------------------- + +--- Class "sl" for keeping string lists. +-- Trying to use string list with anything but strings throws an exception. +sl.sl = class("sl") + +--- Initialize string list [sl:new()]. Merge and unique can't be set both. +-- @param merge Whether entries are to be merged, defaults to false (boolean). +-- @param unique Whether inserting duplicate entries raises errors, +-- defaults to false (boolean). +function sl.sl:initialize(merge, unique) + assert(merge == nil or type(merge) == "boolean") + assert(unique == nil or type(unique) == "boolean") + assert(not (merge and unique)) + + self._merge = merge or false + self._unique = unique or false + self._list = {} +end + +--- Insert an entry into the string list. +-- @param entry The entry. +-- @return True on success, false when the entry is not unique. +function sl.sl:insert(entry) + assert(type(entry) == "string") + + if self._merge then + if self:lookup(entry) then + return true + end + elseif self._unique then + if self:lookup(entry) then + return false + end + end + table.insert(self._list, entry) + return true +end + +--- Remove *all* matching entries from the string list. +-- @param entry The entry. +-- @return True when one or more entries were removed, false otherwise. +function sl.sl:remove(entry) + assert(type(entry) == "string") + local changed, i + + changed = false + i = 1 + while self._list[i] do + if self._list[i] == entry then + table.remove(self._list, i) + changed = true + else + i = i+1 + end + end + + return changed +end + +--- Check whether entry is in string list. +-- @param entry The search entry. +-- @return True if entry is in the string list, false otherwise. +function sl.sl:lookup(entry) + assert(type(entry) == "string") + + for k, v in ipairs(self._list) do + if v == entry then + return true + end + end + + return false +end + +--- Return the number of entries in the string list. +-- @return Number of entries, 0 if empty. +function sl.sl:size() + return #self._list +end + +--- Iterate through the string list in insertion order. +-- @return Iterator function. +function sl.sl:iter_inserted() + + local i = 0 + + return function() + i = i + 1 + return self._list[i] + end +end + +--- Iterate through the string list in alphabetical order. +-- @return Iterator function. +function sl.sl:iter_sorted() + local t = {} + local i = 0 + + for _,v in ipairs(self._list) do + table.insert(t, v) + end + table.sort(t) + + return function() + i = i + 1 + return t[i] + end +end + +--- Create in independent string list copy. +-- @return New string list object. +function sl.sl:copy() + local c = sl.sl:new(self._merge, self._unique) + for e in self:iter_inserted() do + assert(c:insert(e)) + end + assert(self:size() == c:size()) + return c +end + +--- Concatenate the string list in alphabetical order. +-- @param sep Separator, defaults to empty string. +-- @return Concatenated string. +function sl.sl:concat_sorted(sep) + assert(sep == nil or type(sep) == "string") + local first = true + local cat = "" + sep = sep or "" + + for e in self:iter_sorted() do + if first then + cat = e + first = false + else + cat = cat..sep..e + end + end + + return cat +end + +--- Return string list entries as an array, in insertion order. +-- @return Array in insertion order. +function sl.sl:totable_inserted() + local t = {} + for _,v in ipairs(self._list) do + table.insert(t, v) + end + return t +end + +--- Return string list entries as an array, in insertion order. +-- @return Array in insertion order. +function sl.sl:totable_sorted() + return table.sort(self:totable_inserted()) +end + +--[[ +local function selftest() + local s1 = sl.sl:new() + + assert(s1:size() == 0) + assert(s1.class.name == "sl") + + s1:insert("ccc") + s1:insert("bbb") + s1:insert("aaa") + s1:insert("aaa") + + assert(s1:size() == 4) + + local c = 1 + for entry in s1:iter_inserted() do + assert(c <= s1:size() and c > 0) + if c == 1 then assert(entry == "ccc") end + if c == 2 then assert(entry == "bbb") end + if c == 3 then assert(entry == "aaa") end + if c == 4 then assert(entry == "aaa") end + c = c+1 + end + + assert(s1:lookup("foo") == false) + assert(s1:lookup("bbb") == true) + + s1:insert("xxx") + assert(s1:size() == 5) + c = 1 + for entry in s1:iter_sorted() do + assert(c <= s1:size() and c > 0) + if c == 1 then assert(entry == "aaa") end + if c == 2 then assert(entry == "aaa") end + if c == 3 then assert(entry == "bbb") end + if c == 4 then assert(entry == "ccc") end + if c == 5 then assert(entry == "xxx") end + c = c+1 + end + + assert(s1:remove("doesnotexist") == false) + assert(s1:remove("aaa") == true) + assert(s1:size() == 3) + c = 1 + for entry in s1:iter_sorted() do + assert(c <= s1:size() and c > 0) + --e2lib.logf(1, "entry=%s", entry) + if c == 1 then assert(entry == "bbb") end + if c == 2 then assert(entry == "ccc") end + if c == 3 then assert(entry == "xxx") end + c = c+1 + end + + assert(s1:concat_sorted() == "bbbcccxxx") + assert(s1:concat_sorted("y") == "bbbycccyxxx") + + local s2 = sl.sl:new(false, true) + + c = false + for _,v in ipairs({"bbb", "aaa", "xxx", "foo", "bla", "bar", "xxx"}) do + if not s2:insert(v) then + c = true + assert(v == "xxx") + end + end + assert(c == true) + + local s3 = sl.sl:new(true, false) + + for _,v in ipairs({"bbb", "aaa", "xxx", "foo", "bar", "bar", "xxx", "y"}) do + assert(s3:insert(v) == true) + end + assert(s3:size() == 6) + + local s4 = sl.sl:new() + s4:insert("") + s4:insert("") + s4:insert("") + + assert(s4:concat_sorted() == "") + assert(s4:concat_sorted("x") == "xx") +end + +selftest() +--]] + +return strict.lock(sl) + +-- vim:sw=4:sts=4:et: diff --git a/local/source.lua b/local/source.lua new file mode 100644 index 0000000..7b79ecc --- /dev/null +++ b/local/source.lua @@ -0,0 +1,433 @@ +--- Source base class. Implements the base source class and config loader. +-- @module local.source + +-- Copyright (C) 2007-2014 emlix GmbH, see file AUTHORS +-- +-- This file is part of e2factory, the emlix embedded build system. +-- For more information see http://www.e2factory.org +-- +-- e2factory is a registered trademark of emlix GmbH. +-- +-- e2factory is free software: you can redistribute it and/or modify it under +-- the terms of the GNU General Public License as published by the +-- Free Software Foundation, either version 3 of the License, or (at your +-- option) any later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +-- more details. + +local source = {} +package.loaded["source"] = source + +local cache = require("cache") +local class = require("class") +local e2lib = require("e2lib") +local e2tool = require("e2tool") +local environment = require("environment") +local err = require("err") +local licence = require("licence") +local sl = require("sl") +local strict = require("strict") + +--- Dictionary indexed by source type, derived source class. +local source_types = {} + +--- Source base class. +source.basic_source = class("basic_source") + +--- Source base constructor. Assert error on invalid input. +-- @param rawsrc Source config dict containing at least "name" and "type" +-- attributes. +function source.basic_source:initialize(rawsrc) + assert(type(rawsrc) == "table") + assert(type(rawsrc.name) == "string" and rawsrc.name ~= "") + assert(type(rawsrc.type) == "string" and rawsrc.type ~= "") + + self._name = rawsrc.name + self._type = rawsrc.type + self._licences = false + self._env = false +end + +--- Get name. +-- @return Name of source (Ex: group.result). +function source.basic_source:get_name() + assert(type(self._name) == "string") + return self._name +end + +--- Get name as directory path. +-- @return Path of source (ex: group/result). +function source.basic_source:get_name_as_path() + assert(type(self._name) == "string") + local p = e2tool.src_res_name_to_path(self._name) + assert(type(p) == "string") + return p +end + +--- Get type of source. +-- @return Type of source (ex: "files", "git", ...) +function source.basic_source:get_type() + return self._type +end + +--- Set licence array. +-- @param licences String list of licence names (sl). +function source.basic_source:set_licences(licences) + assert(type(licences) == "table" and licences.class.name == "sl") + self._licences = licences:copy() +end + +--- Get licence array. Must be set before calling get_licences(). Note that +-- this returns all licences used in a source. Some sources may have more +-- detailed licensing information which can be accessed by other means. +-- @return String list of licence names (sl). +function source.basic_source:get_licences() + assert(type(self._licences) == "table") + return self._licences:copy() +end + +--- Set env object. +-- @param env Env object. +function source.basic_source:set_env(env) + assert(type(env) == "table") + self._env = env +end + +--- Get env object. Must be set before calling get_env(). +-- @return Env object. +function source.basic_source:get_env() + assert(type(self._env) == "table") + return self._env +end + +--- Abstract sourceid method. Every child class must overwrite this +-- method with an implementation. Calling this method throws an error. +-- @param sourceset Source set (ex: "tag", "branch", ...) +-- @return Sourceid string (usually a hash value) or false on error. +-- @return Error object on failure. +function source.basic_source:sourceid(sourceset) + error(err.new("called sourceid() of source base class, type %s name %s", + self._type, self._name)) +end + +--- Abstract display method. Every child class must overwrite this +-- method with an implementation. Calling this method throws an error. +-- @return Array of strings containing free form information about source. +function source.basic_source:display() + error(err.new("called display() of source base class, type %s name %s", + self._type, self._name)) +end + +--- Dictionary holding all source objects indexed by their name. +source.sources = {} +--- Array holding all source objects in alphabetical order. +source.sources_sorted = {} + +--- Gather source paths. +-- @param info Info table. +-- @param basedir Nil or directory from where to start scanning for more +-- sources. Only for recursion. +-- @param sources Nil or table of source paths. Only for recursion. +-- @return Table with source paths, or false on error. +-- @return Error object on failure. +local function gather_source_paths(info, basedir, sources) + local rc, re + local currdir, sdir, sconfig, s + sources = sources or {} + + currdir = e2tool.sourcedir(basedir, info.root) + for entry, re in e2lib.directory(currdir) do + if not entry then + return false, re + end + + if basedir then + entry = e2lib.join(basedir, entry) + end + + sdir = e2tool.sourcedir(entry, info.root) + sconfig = e2tool.sourceconfig(entry, info.root) + s = e2lib.stat(sdir, false) + if s.type == "directory" then + if e2lib.exists(sconfig) then + table.insert(sources, entry) + else + -- try sub directory + rc, re = gather_source_paths(info, entry, sources) + if not rc then + return false, re + end + end + end + end + + return sources +end + +--- Search, load and verify all source configs. On success, all sources +--available as objects in source.sources[] etc. +-- @param info Info table +-- @return True on success, false on error. +-- @return Error object on failure. +function source.load_source_configs(info) + local rc, re, e + local g, rawsrc, loadcnt, configs, path, src + + e = err.new("error loading source configuration") + + configs, re = gather_source_paths(info) + if not configs then + return false, e:cat(re) + end + + for _,cfg in ipairs(configs) do + rc, re = e2tool.verify_src_res_pathname_valid_chars(cfg) + if not rc then + e:append("invalid source file name: %s", cfg) + e:cat(re) + return false, e + end + + rawsrc = nil + loadcnt = 0 + g = { + e2source = function(data) rawsrc = data loadcnt = loadcnt + 1 end, + env = info.env, + string = e2lib.safe_string_table(), + } + + path = e2tool.sourceconfig(cfg, info.root) + rc, re = e2lib.dofile2(path, g) + if not rc then + return false, e:cat(re) + end + + if type(rawsrc) ~= "table" then + return false, e:append("source %q is missing an e2source table", cfg) + end + + if loadcnt > 1 then + return false, e:append("duplicate source config in %q", cfg) + end + + if not rawsrc.name then + e2lib.warnf("WDEFAULT", "`name' attribute missing in source config.") + e2lib.warnf("WDEFAULT", " Defaulting to directory name") + rawsrc.name = e2tool.src_res_path_to_name(cfg) + end + + if rawsrc.name ~= e2tool.src_res_path_to_name(cfg) then + return false, e:append( + "source name %q must match source directory name %q", + rawsrc.name, e2tool.src_res_path_to_name(cfg)) + end + + rc, re = e2tool.verify_src_res_name_valid_chars(rawsrc.name) + if not rc then + e:append("invalid source name: %s", rawsrc.name) + e:cat(re) + return false, e + end + + if source.sources[rawsrc.name] then + return false, e:append("duplicate source: %s", rawsrc.name) + end + + -- source with no type field is treated as file source + if not rawsrc.type then + rawsrc.type = "files" + e2lib.warnf("WDEFAULT", "in source %s", rawsrc.name) + e2lib.warnf("WDEFAULT", " type attribute defaults to `files'") + end + + if not source_types[rawsrc.type] then + return false, + e:append("don't know how to handle %q source", rawsrc.type) + end + + src = source_types[rawsrc.type] + + -- src:new(rawsrc) + rc, re = e2lib.trycall(src.new, src, rawsrc) + if not rc then + e = err.new("error in source %q", rawsrc.name) + return false, e:cat(re) + end + + src = re + assert(type(src) == "table") + source.sources[src:get_name()] = src + end + + for sourcename,_ in pairs(source.sources) do + table.insert(source.sources_sorted, sourcename) + end + table.sort(source.sources_sorted) + + return true +end + +--- Register a source class. A type can only be registered once. +-- @param typ Source type name. +-- @param source_class Class derived from basic_source. +-- @return True on success, false on error. +-- @return Error object on failure. +function source.register_source_class(typ, source_class) + assert(type(typ) == "string" and typ ~= "") + assert(type(source_class) == "table") + + if source_types[typ] then + return false, err.new("source %q already registered", typ) + end + + source_types[typ] = source_class + + return true +end + +--- Validate licences attribute in rawsrc and set licences in src if successful. +-- @param rawsrc e2source config table +-- @param src Object of class basic_source. +-- @return True on success, false on error. +-- @return Error object on failure. +function source.generic_source_validate_licences(rawsrc, src) + assert(type(rawsrc) == "table" and rawsrc.name and rawsrc.type) + assert(type(src) == "table") + + local rc, re, licences + + licences = sl.sl:new(false, true --[[unique]]) + + --[[if not rawsrc.licences and rawsrc.licence then + e2lib.warnf("WDEPRECATED", "in source %s:", src.name) + e2lib.warnf("WDEPRECATED", + " licence attribute is deprecated. Replace by licences.") + src.licences = src.licence + end]] + + if rawsrc.licences == nil then + e2lib.warnf("WDEFAULT", "in source %s:", rawsrc.name) + e2lib.warnf("WDEFAULT", + " licences attribute missing. Defaulting to empty list.") + rawsrc.licences = {} + elseif type(rawsrc.licences) == "string" then + e2lib.warnf("WDEPRECATED", "in source %s:", rawsrc.name) + e2lib.warnf("WDEPRECATED", + " licences attribute is not in table format. Converting.") + rawsrc.licences = { rawsrc.licences } + end + + if type(rawsrc.licences) ~= "table" then + return false, err.new("licences attribute must be a table") + end + + rc, re = e2lib.vrfy_listofstrings(rawsrc.licences, "licences attribute", + true, false) + if not rc then + return false, re + end + + for _,licencename in ipairs(rawsrc.licences) do + if not licence.licences[licencename] then + return false, err.new("unknown licence: %s", licencename) + end + assert(licences:insert(licencename)) + end + + src:set_licences(licences) + + return true +end + +--- Helper to validate and set env in src. +-- @param rawsrc e2source config table. +-- @param src Source object. +-- @return True on success, false on error. +-- @return Error object on failure. +function source.generic_source_validate_env(rawsrc, src) + assert(type(rawsrc) == "table" and rawsrc.name and rawsrc.type) + assert(type(src) == "table") + + local newenv = environment.new() + + if rawsrc.env ~= nil and type(rawsrc.env) ~= "table" then + return false, err.new("source has invalid `env' attribute") + end + + if not rawsrc.env then + e2lib.warnf("WDEFAULT", + "source has no `env' attribute. Defaulting to empty dictionary") + rawsrc.env = {} + end + + for k, v in pairs(rawsrc.env) do + if type(k) ~= "string" then + return false, err.new( + "in `env' dictionary: key is not a string: %s", tostring(k)) + elseif type(v) ~= "string" then + return false, err.new( + "in `env' dictionary: value is not a string: %s", tostring(v)) + else + newenv:set(k, v) + end + end + + src:set_env(newenv) + + return true +end + +--- Helper to validate server. +-- @param rawsrc e2source config table +-- @param ismandatory Whether rawsrc containing a server attr is mandatory. +-- @return True on success, false on error. +-- @return Error object on failure. +function source.generic_source_validate_server(rawsrc, ismandatory) + assert(type(rawsrc) == "table" and rawsrc.name and rawsrc.type) + assert(type(ismandatory) == "boolean") + + local info = e2tool.info() + + if ismandatory and rawsrc.server == nil then + return false, err.new("source has no `server' attribute") + end + + if rawsrc.server ~= nil and type(rawsrc.server) ~= "string" then + return false, err.new("'server' attribute must be a string") + end + + if rawsrc.server and (not cache.valid_server(info.cache, rawsrc.server)) then + return false, err.new("invalid server: %s", rawsrc.server) + end + + return true +end + +--- Helper to validate working attribute. +-- @param rawsrc e2source config table +-- @return True on success, false on error. +-- @return Error object on failure. +function source.generic_source_validate_working(rawsrc) + assert(type(rawsrc) == "table" and rawsrc.name and rawsrc.type) + + if rawsrc.working ~= nil and not type(rawsrc.working) == "string" then + return false, err.new("'working' attribute must be a string") + end + + if rawsrc.working == nil then + rawsrc.working = e2lib.join("in", rawsrc.name) + + e2lib.warnf("WDEFAULT", "in source %s:", rawsrc.name) + e2lib.warnf("WDEFAULT", " `working' attribute defaults to '%s'.", + rawsrc.working) + end + + return true +end + +return strict.lock(source) + +-- vim:sw=4:sts=4:et: diff --git a/plugins/cvs.lua b/plugins/cvs.lua index 944b5e8..d0eedac 100644 --- a/plugins/cvs.lua +++ b/plugins/cvs.lua @@ -30,7 +30,9 @@ local cvs = {} local cache = require("cache") +local class = require("class") local e2lib = require("e2lib") +local e2tool = require("e2tool") local eio = require("eio") local err = require("err") local hash = require("hash") @@ -39,13 +41,30 @@ local scm = require("scm") local strict = require("strict") local tools = require("tools") local url = require("url") +local source = require("source") plugin_descriptor = { description = "CVS SCM Plugin", - init = function (ctx) scm.register("cvs", cvs) return true end, + init = function (ctx) + local rc, re + + rc, re = source.register_source_class("cvs", cvs.cvs_source) + if not rc then + return false, re + end + + rc, re = scm.register("cvs", cvs) + if not rc then + return false, re + end + + return true + end, exit = function (ctx) return true end, } +cvs.cvs_source = class("cvs_source", source.basic_source) + local function cvs_tool(argv, workdir) local rc, re, cvscmd, cvsflags, rsh @@ -77,65 +96,196 @@ local function cvs_tool(argv, workdir) return e2lib.callcmd_log(cvscmd, workdir, { CVS_RSH=rsh }) end ---- validate source configuration, log errors to the debug log --- @param info the info table --- @param sourcename the source name --- @return bool -function cvs.validate_source(info, sourcename) - local rc, re = scm.generic_source_validate(info, sourcename) + +function cvs.cvs_source:initialize(rawsrc) + assert(type(rawsrc) == "table") + assert(type(rawsrc.name) == "string" and #rawsrc.name > 0) + assert(type(rawsrc.type) == "string" and rawsrc.type == "cvs") + + local rc, re + + source.basic_source.initialize(self, rawsrc) + + self._branch = false + self._cvsroot = false + self._module = false + self._server = false + self._tag = false + self._working = false + self._sourceids = { + ["working-copy"] = "working-copy", + } + + rc, re = e2lib.vrfy_dict_exp_keys(rawsrc, "e2source", { + "branch", + "cvsroot", + "env", + "licences", + "module", + "name", + "server", + "tag", + "type", + "working", + }) if not rc then - -- error in generic configuration. Don't try to go on. - return false, re + error(re) end - local src = info.sources[ sourcename ] - if not src.sourceid then - src.sourceid = {} + + rc, re = source.generic_source_validate_licences(rawsrc, self) + if not rc then + error(re) end - local e = err.new("in source %s:", sourcename) - rc, re = scm.generic_source_default_working(info, sourcename) + + rc, re = source.generic_source_validate_env(rawsrc, self) if not rc then - return false, e:cat(re) + error(re) end - e:setcount(0) - -- XXX should move the default value out of the validate function - if not src.server then - e:append("source has no `server' attribute") + + rc, re = source.generic_source_validate_server(rawsrc, true) + if not rc then + error(re) end - if not src.licences then - e:append("source has no `licences' attribute") + self._server = rawsrc.server + + rc, re = source.generic_source_validate_working(rawsrc) + if not rc then + error(re) end - if not src.cvsroot then + self._working = rawsrc.working + + if rawsrc.cvsroot == nil then e2lib.warnf("WDEFAULT", "in source %s:", sourcename) e2lib.warnf("WDEFAULT", " source has no `cvsroot' attribute, defaulting to the server path") - src.cvsroot = "." - end - if not src.cvsroot then - e:append("source has no `cvsroot' attribute") - end - if src.remote then - e:append("source has `remote' attribute, not allowed for cvs sources") + self._cvsroot = "." + elseif type(rawsrc.cvsroot) == "string" then + self._cvsroot = rawsrc.cvsroot + else + error(err.new("'cvsroot' must be a string")) end - if not src.branch then - e:append("source has no `branch' attribute") + + for _,attr in ipairs({ "branch", "module", "tag" }) do + if rawsrc[attr] == nil then + error(err.new("source has no `%s' attribute", attr)) + elseif type(rawsrc[attr]) ~= "string" then + error(err.new("'%s' must be a string", attr)) + elseif rawsrc[attr] == "" then + error(err.new("'%s' may not be empty", attr)) + end end - if type(src.tag) ~= "string" then - e:append("source has no `tag' attribute or tag attribute has wrong type") + self._branch = rawsrc.branch + self._module = rawsrc.module + self._tag = rawsrc.tag +end + +function cvs.cvs_source:get_working() + assert(type(self._working) == "string") + + return self._working +end + +function cvs.cvs_source:get_module() + assert(type(self._module) == "string") + + return self._module +end + +function cvs.cvs_source:get_branch() + assert(type(self._branch) == "string") + + return self._branch +end + +function cvs.cvs_source:get_tag() + assert(type(self._tag) == "string") + + return self._tag +end + +function cvs.cvs_source:get_server() + assert(type(self._server) == "string") + + return self._server +end + +function cvs.cvs_source:get_cvsroot() + assert(type(self._cvsroot) == "string") + + return self._cvsroot +end + +function cvs.cvs_source:sourceid(sourceset) + assert(type(sourceset) == "string" and #sourceset > 0) + + local rc, re, hc, lid, info, licences + + if self._sourceids[sourceset] then + return self._sourceids[sourceset] end - if not src.module then - e:append("source has no `module' attribute") + + info = e2tool.info() + assert(type(info) == "table") + + hc = hash.hash_start() + hash.hash_line(hc, self._name) + hash.hash_line(hc, self._type) + hash.hash_line(hc, self._env:id()) + licences = self:get_licences() + for licencename in licences:iter_sorted() do + lid, re = licence.licences[licencename]:licenceid(info) + if not lid then + return false, re + end + hash.hash_line(hc, lid) end - if not src.working then - e:append("source has no `working' attribute") + -- cvs specific + if sourceset == "tag" and self._tag ~= "^" then + -- we rely on tags being unique with cvs + hash.hash_line(hc, self._tag) + else + -- the old function took a hash of the CVS/Entries file, but + -- forgot the subdirecties' CVS/Entries files. We might + -- reimplement that once... + return false, err.new("cannot calculate sourceid for source set %s", + sourceset) end - local rc, re = tools.check_tool("cvs") - if not rc then - e:cat(re) + hash.hash_line(hc, self._server) + hash.hash_line(hc, self._cvsroot) + hash.hash_line(hc, self._module) + + self._sourceids[sourceset] = hash.hash_finish(hc) + + return self._sourceids[sourceset] +end + +function cvs.cvs_source:display() + local licences + local d = {} + + self:sourceid("tag") + self:sourceid("branch") + + table.insert(d, string.format("type = %s", self:get_type())) + table.insert(d, string.format("branch = %s", self._branch)) + table.insert(d, string.format("tag = %s", self._tag)) + table.insert(d, string.format("server = %s", self._server)) + table.insert(d, string.format("cvsroot = %s", self._cvsroot)) + table.insert(d, string.format("module = %s", self._module)) + table.insert(d, string.format("working = %s", self._working)) + + licences = self:get_licences() + for licencename in licences:iter_sorted() do + table.insert(d, string.format("licence = %s", licencename)) end - if e:getcount() > 0 then - return false, e + + for sourceset, sid in pairs(self._sourceids) do + if sid then + table.insert(d, string.format("sourceid [%s] = %s", sourceset, sid)) + end end - return true, nil + + return d end --- Build the cvsroot string. @@ -146,9 +296,9 @@ end local function mkcvsroot(info, sourcename) local cvsroot, src, surl, u, re - src = info.sources[sourcename] + src = source.sources[sourcename] - surl, re = cache.remote_url(info.cache, src.server, src.cvsroot) + surl, re = cache.remote_url(info.cache, src:get_server(), src:get_cvsroot()) if not surl then return false, e:cat(re) end @@ -176,7 +326,7 @@ function cvs.fetch_source(info, sourcename) local rc, re, e, src, cvsroot, workdir, argv e = err.new("fetching source failed: %s", sourcename) - src = info.sources[sourcename] + src = source.sources[sourcename] cvsroot, re = mkcvsroot(info, sourcename) if not cvsroot then @@ -185,23 +335,23 @@ function cvs.fetch_source(info, sourcename) -- split the working directory into dirname and basename as some cvs clients -- don't like slashes (e.g. in/foo) in their checkout -d argument - workdir = e2lib.dirname(e2lib.join(info.root, src.working)) + workdir = e2lib.dirname(e2lib.join(info.root, src:get_working())) argv = { "-d", cvsroot, "checkout", "-R", - "-d", e2lib.basename(src.working), + "-d", e2lib.basename(src:get_working()), } -- always fetch the configured branch, as we don't know the build mode here. -- HEAD has special meaning to cvs - if src.branch ~= "HEAD" then + if src:get_branch() ~= "HEAD" then table.insert(argv, "-r") - table.insert(argv, src.branch) + table.insert(argv, src:get_branch()) end - table.insert(argv, src.module) + table.insert(argv, src:get_module()) rc, re = cvs_tool(argv, workdir) if not rc or rc ~= 0 then @@ -210,44 +360,44 @@ function cvs.fetch_source(info, sourcename) return true end -function cvs.prepare_source(info, sourcename, source_set, buildpath) +function cvs.prepare_source(info, sourcename, sourceset, buildpath) local rc, re, e, src, cvsroot, argv e = err.new("cvs.prepare_source failed") - src = info.sources[sourcename] + src = source.sources[sourcename] cvsroot, re = mkcvsroot(info, sourcename) if not cvsroot then return false, re end - if source_set == "tag" or source_set == "branch" then + if sourceset == "tag" or sourceset == "branch" then argv = { "-d", cvsroot, "export", "-R", - "-d", src.name, + "-d", src:get_name(), "-r", } - if source_set == "branch" or - (source_set == "lazytag" and src.tag == "^") then - table.insert(argv, src.branch) - elseif (source_set == "tag" or source_set == "lazytag") and - src.tag ~= "^" then - table.insert(argv, src.tag) + if sourceset == "branch" or + (sourceset == "lazytag" and src:get_tag() == "^") then + table.insert(argv, src:get_branch()) + elseif (sourceset == "tag" or sourceset == "lazytag") and + src:get_tag() ~= "^" then + table.insert(argv, src:get_tag()) else return false, e:cat(err.new("source set not allowed")) end - table.insert(argv, src.module) + table.insert(argv, src:get_module()) rc, re = cvs_tool(argv, buildpath) if not rc or rc ~= 0 then return false, e:cat(re) end - elseif source_set == "working-copy" then - rc, re = e2lib.cp(e2lib.join(info.root, src.working), - e2lib.join(buildpath, src.name), true) + elseif sourceset == "working-copy" then + rc, re = e2lib.cp(e2lib.join(info.root, src:get_working()), + e2lib.join(buildpath, src:get_name()), true) if not rc then return false, e:cat(re) end @@ -261,9 +411,9 @@ function cvs.update(info, sourcename) local rc, re, e, src, workdir, argv e = err.new("updating source '%s' failed", sourcename) - src = info.sources[sourcename] + src = source.sources[sourcename] - workdir = e2lib.join(info.root, src.working) + workdir = e2lib.join(info.root, src:get_working()) argv = { "update", "-R" } rc, re = cvs_tool(argv, workdir) @@ -271,12 +421,12 @@ function cvs.update(info, sourcename) return false, e:cat(re) end - return true, nil + return true end function cvs.working_copy_available(info, sourcename) - local src = info.sources[sourcename] - local dir = string.format("%s/%s", info.root, src.working) + local src = source.sources[sourcename] + local dir = e2lib.join(info.root, src:get_working()) return e2lib.isdir(dir) end @@ -284,80 +434,6 @@ function cvs.has_working_copy(info, sourcename) return true end ---- create a table of lines for display --- @param info the info structure --- @param sourcename string --- @return a table, nil on error --- @return an error object on failure -function cvs.display(info, sourcename) - local src = info.sources[sourcename] - local rc, re - local display = {} - - display[1] = string.format("type = %s", src.type) - display[2] = string.format("branch = %s", src.branch) - display[3] = string.format("tag = %s", src.tag) - display[4] = string.format("server = %s", src.server) - display[5] = string.format("cvsroot = %s", src.cvsroot) - display[6] = string.format("module = %s", src.module) - display[7] = string.format("working = %s", src.working) - local i = 8 - for _,l in ipairs(src.licences) do - display[i] = string.format("licence = %s", l) - i = i + 1 - end - for k,v in pairs(src.sourceid) do - if v then - display[i] = string.format("sourceid [%s] = %s", k, v) - i = i + 1 - end - end - return display, nil -end - -function cvs.sourceid(info, sourcename, source_set) - local src = info.sources[sourcename] - local rc, re, lid - - if source_set == "working-copy" then - src.sourceid[source_set] = "working-copy" - end - if src.sourceid[source_set] then - return true, nil, src.sourceid[source_set] - end - local e = err.new("calculating sourceid failed for source %s", - sourcename) - local hc = hash.hash_start() - hash.hash_line(hc, src.name) - hash.hash_line(hc, src.type) - hash.hash_line(hc, src._env:id()) - for _,ln in ipairs(src.licences) do - lid, re = licence.licences[ln]:licenceid(info) - if not lid then - return false, e:cat(re) - end - hash.hash_line(hc, lid) - end - -- cvs specific - if source_set == "tag" and src.tag ~= "^" then - -- we rely on tags being unique with cvs - hash.hash_line(hc, src.tag) - else - -- the old function took a hash of the CVS/Entries file, but - -- forgot the subdirecties' CVS/Entries files. We might - -- reimplement that once... - e:append("cannot calculate sourceid for source set %s", - source_set) - return false, e - end - hash.hash_line(hc, src.server) - hash.hash_line(hc, src.cvsroot) - hash.hash_line(hc, src.module) - -- skip src.working - src.sourceid[source_set] = hash.hash_finish(hc) - return true, nil, src.sourceid[source_set] -end - function cvs.toresult(info, sourcename, sourceset, directory) -- /source/.tar.gz -- /makefile @@ -368,7 +444,7 @@ function cvs.toresult(info, sourcename, sourceset, directory) if not rc then return false, e:cat(re) end - local src = info.sources[sourcename] + local src = source.sources[sourcename] -- write makefile local makefile = "Makefile" local source = "source" @@ -400,7 +476,7 @@ function cvs.toresult(info, sourcename, sourceset, directory) return false, e:cat(re) end -- create a tarball in the final location - local archive = string.format("%s.tar.gz", src.name) + local archive = string.format("%s.tar.gz", src:get_name()) rc, re = e2lib.tar({ "-C", tmpdir ,"-czf", sourcedir .. "/" .. archive, sourcename }) if not rc then @@ -409,7 +485,9 @@ function cvs.toresult(info, sourcename, sourceset, directory) -- write licences local destdir = string.format("%s/licences", directory) local fname = string.format("%s/%s.licences", destdir, archive) - local licence_list = table.concat(src.licences, "\n") .. "\n" + local licenses = src:get_licences() + local licence_list = licenses:concat_sorted("\n").."\n" + rc, re = e2lib.mkdir_recursive(destdir) if not rc then return false, e:cat(re) diff --git a/plugins/files.lua b/plugins/files.lua index 89c33b3..7c062a3 100644 --- a/plugins/files.lua +++ b/plugins/files.lua @@ -30,75 +30,125 @@ local files = {} local cache = require("cache") +local class = require("class") local e2lib = require("e2lib") local e2tool = require("e2tool") local eio = require("eio") +local environment = require("environment") local err = require("err") local hash = require("hash") +local licence = require("licence") local scm = require("scm") +local sl = require("sl") +local source = require("source") local strict = require("strict") local tools = require("tools") -local licence = require("licence") + plugin_descriptor = { description = "Files SCM Plugin", - init = function (ctx) scm.register("files", files) return true end, + init = function (ctx) + local rc, re + + rc, re = source.register_source_class("files", files.files_source) + if not rc then + return false, re + end + + rc, re = scm.register("files", files) + if not rc then + return false, re + end + + return true + end, exit = function (ctx) return true end, } ---- validate source configuration, log errors to the debug log --- @param info the info table --- @param sourcename the source name --- @return bool -function files.validate_source(info, sourcename) - local rc1 = true -- the return value - local rc, e = scm.generic_source_validate(info, sourcename) +files.files_source = class("files_source", source.basic_source) + +function files.files_source:initialize(rawsrc) + assert(type(rawsrc) == "table") + assert(type(rawsrc.name) == "string" and #rawsrc.name > 0) + assert(type(rawsrc.type) == "string" and rawsrc.type == "files") + + local rc, re, e, info + + source.basic_source.initialize(self, rawsrc) + + self._files = {} + self._sourceid = false + + rc, re = e2lib.vrfy_dict_exp_keys(rawsrc, "e2source config", { + "env", + "file", + "licences", + "name", + "server", + "type", + }) + if not rc then + error(re) + end + + rc, re = source.generic_source_validate_licences(rawsrc, self) + if not rc then + error(re) + end + + rc, re = source.generic_source_validate_env(rawsrc, self) + if not rc then + error(re) + end + + rc, re = source.generic_source_validate_server(rawsrc, false) if not rc then - return false, e + error(re) end - e = err.new("in source %s:", sourcename) - e:setcount(0) - local src = info.sources[ sourcename ] - if type(src.file) ~= "table" then - return false, e:cat(err.new("source has no valid `file' attribute")) + + if type(rawsrc.file) ~= "table" then + error(err.new("`file' attribute must be a table")) end - for _,f in pairs(src.file) do + e = err.new("error in file list of source") + for _,f in ipairs(rawsrc.file) do if type(f) ~= "table" then - e:append("%s: source has invalid file entry in `file' attribute", - sourcename) - break - end - -- catch deprecated configuration - if f.name then - e:append("source has file entry with `name' attribute") + error(e:append("`file' attribute must be a table")) end - if (not f.licences) and src.licences then - f.licences = src.licences - end - if (not f.server) and src.server then - f.server = src.server - end - if not f.licences then - e:append("source has file entry without `licences' attribute") + + rc, re = e2lib.vrfy_dict_exp_keys(f, "e2source config", + { + "copy", + "licences", + "location", + "patch", + "server", + "sha1", + "unpack", + }) + if not rc then + error(e:cat(re)) end - for _,l in ipairs(f.licences) do - if not licence.licences[l] then - e:append("invalid licence assigned to file: %s", l) - end + + + if (not f.server) and rawsrc.server then + f.server = rawsrc.server end + + info = e2tool.info() + if not f.server then - e:append("source has file entry without `server' attribute") + error(e:append("file entry without `server' attribute")) end if f.server and (not cache.valid_server(info.cache, f.server)) then - e:append("invalid server: %s", f.server) + error(e:append("invalid server: %s", f.server)) end if not f.location then - e:append("source has file entry without `location' attribute") + error(e:append("file entry without `location' attribute")) end if f.server ~= info.root_server_name and not f.sha1 then - e:append("source has file entry for remote file without `sha1` ".. - "attribute") + error(e:append("file entry for remote file without ".. + "`sha1` attribute")) end local attrcnt = 0 @@ -107,33 +157,189 @@ function files.validate_source(info, sourcename) attrcnt = attrcnt + 1 if type(f[attr]) ~= "string" then - e:append("'%s' in file entry of source must be a string", attr) - break + error(e:append( + "'%s' in file entry of source must be a string", attr)) end end end if attrcnt == 0 then - e:append("source has file entry without `unpack, copy or patch' " .. - "attribute") + error(e:append("file entry without ".. + "unpack, copy or patch attribute")) elseif attrcnt > 1 then - e:append("source has file entry with conflicting unpack, copy or".. - " patch attributes") + error(e:append("file entry with conflicting ".. + "unpack, copy or patch attributes")) end - if f.checksum_file then - e2lib.warnf("WDEPRECATED", "in source %s:", sourcename) - e2lib.warnf("WDEPRECATED", - " checksum_file attribute is deprecated and no longer used") - f.checksum_file = nil + assert(type(f.location) == "string" and f.location ~= "") + assert(type(f.server) == "string" and f.server ~= "") + assert(f.sha1 == nil or (type(f.sha1) == "string" and #f.sha1 == 40)) + + -- per file licences -- + local laerr = string.format("%s:%s licences attribute", + f.server, f.location) + local llist, licences + + if f.licences == nil then + f.licences = self:get_licences():copy() + elseif type(f.licences == "table") then + rc, re = e2lib.vrfy_listofstrings(f.licences, laerr, true, false) + if not rc then + error(e:cat(re)) + end + + licences = self:get_licences() + llist = sl.sl:new(false, true) + + for _,licencename in ipairs(f.licences) do + if not licence.licences[licencename] then + error(e:append("%s has unknown licence: %q", + laerr, licencename)) + end + + -- Make sure the _licences list contains every licence in the + -- entire source. Duplicates are rejected by unique string list. + licences:insert(licencename) + assert(llist:insert(licencename)) + end + + self:set_licences(licences) + f.licences = llist + else + error(e:append("%s must be a table", laerr)) + end + + if f.unpack then + assert(type(f.unpack) == "string") + + table.insert(self._files, { + location=f.location, + server=f.server, + sha1=f.sha1, + unpack=f.unpack, + licences=f.licences, + }) + elseif f.copy then + assert(type(f.copy) == "string") + + table.insert(self._files, { + location=f.location, + server=f.server, + sha1=f.sha1, + copy=f.copy, + licences=f.licences, + }) + elseif f.patch then + assert(type(f.patch) == "string") + + table.insert(self._files, { + location=f.location, + server=f.server, + sha1=f.sha1, + patch=f.patch, + licences=f.licences, + }) + else + assert("internal error" == true) end end +end + +function files.files_source:file_iter() + local i = 0 + + return function () + i = i + 1 + + if self._files[i] then + -- return a copy so nobody can mess with the internals + local f = { + location = self._files[i].location, + server = self._files[i].server, + sha1 = self._files[i].sha1, + licences = self._files[i].licences:copy() + } + for _,attr in ipairs({ "copy", "unpack", "patch" }) do + if self._files[i][attr] then + f[attr] = self._files[i][attr] + break + end + end + return f + end - if e:getcount() > 0 then - return false, e + return nil end - return true, nil +end + +function files.files_source:sourceid(sourceset --[[always ignored for files]]) + local hc, info, licences + + if self._sourceid then + return self._sourceid + end + + info = e2tool.info() + assert(info) + + hc = hash.hash_start() + hash.hash_line(hc, self._name) + hash.hash_line(hc, self._type) + hash.hash_line(hc, self._env:id()) + + for f in self:file_iter() do + local fileid, re = e2tool.fileid(info, f) + if not fileid then + return false, re + end + hash.hash_line(hc, fileid) + hash.hash_line(hc, f.location) + hash.hash_line(hc, f.server) + hash.hash_line(hc, tostring(f.unpack)) + hash.hash_line(hc, tostring(f.patch)) + hash.hash_line(hc, tostring(f.copy)) + + -- per file licence list + for licencename in f.licences:iter_sorted() do + local lid, re = licence.licences[licencename]:licenceid(info) + if not lid then + return false, re + end + hash.hash_line(hc, lid) + end + end + + self._sourceid = hash.hash_finish(hc) + + return self._sourceid +end + +--- create a table of lines for display +-- @return a table +function files.files_source:display() + local s, sid, d, licences + + self:sourceid() + + d = {} + table.insert(d, string.format("type = %s", self:get_type())) + + for f in self:file_iter() do + s = string.format("file = %s:%s", f.server, f.location) + table.insert(d, s) + end + + licences = self:get_licences() + for licencename in licences:iter_sorted() do + table.insert(d, string.format("licence = %s", licencename)) + end + + if self._sourceid then + table.insert(d, string.format("sourceid = %s", self._sourceid)) + end + + return d end --- cache files for a source @@ -142,25 +348,25 @@ end -- @return bool -- @return nil, an error string on error function files.cache_source(info, sourcename) - local rc, e - local s = info.sources[sourcename] + local rc, re + local src = source.sources[sourcename] + -- cache all files for this source - for i,f in pairs(s.file) do + for f in src:file_iter() do e2lib.logf(4, "files.cache_source: caching file %s:%s", f.server, f.location) local flags = { cache = true } if f.server ~= info.root_server_name then - local rc, e = cache.cache_file(info.cache, f.server, - f.location, flags) + rc, re = cache.cache_file(info.cache, f.server, f.location, flags) if not rc then - return false, e + return false, re end else e2lib.logf(4, "not caching %s:%s (stored locally)", f.server, f.location) end end - return true, nil + return true end function files.fetch_source(info, sourcename) @@ -337,8 +543,9 @@ function files.prepare_source(info, sourcename, sourceset, buildpath) local rc, re local e = err.new("error preparing source: %s", sourcename) local symlink = nil - local s = info.sources[sourcename] - for _,file in ipairs(info.sources[sourcename].file) do + local src = source.sources[sourcename] + + for file in src:file_iter() do if file.sha1 then rc, re = e2tool.verify_hash(info, file.server, file.location, file.sha1) if not rc then @@ -438,74 +645,6 @@ function files.prepare_source(info, sourcename, sourceset, buildpath) return true, nil end ---- create a table of lines for display --- @param info the info structure --- @param sourcename string --- @return a table, nil on error --- @return an error string on failure -function files.display(info, sourcename) - local src = info.sources[sourcename] - local display = {} - - display[1] = string.format("type = %s", src.type) - local i = 2 - for _,f in pairs(src.file) do - display[i] = string.format("file = %s:%s", f.server, f.location) - i = i + 1 - end - for _,l in ipairs(src.licences) do - display[i] = string.format("licence = %s", l) - i = i + 1 - end - if src.sourceid then - display[i] = string.format("sourceid = %s", src.sourceid) - i = i + 1 - end - return display -end - ---- calculate an id for a source --- @param info --- @param sourcename --- @param sourceset --- @return string: the source id, nil on error --- @return an error string on error -function files.sourceid(info, sourcename, sourceset) - local rc, re - local e = err.new("error calculating sourceid for source: %s", - sourcename) - local src = info.sources[sourcename] - if src.sourceid then - return true, nil, src.sourceid - end - -- sourceset is ignored for files sources - local hc = hash.hash_start() - hash.hash_line(hc, src.name) - hash.hash_line(hc, src.type) - hash.hash_line(hc, src._env:id()) - for _,ln in ipairs(src.licences) do - local lid, re = licence.licences[ln]:licenceid(info) - if not lid then - return false, re - end - hash.hash_line(hc, lid) - end - for _,f in ipairs(src.file) do - local fileid, re = e2tool.fileid(info, f) - if not fileid then - return false, e:cat(re) - end - hash.hash_line(hc, fileid) - hash.hash_line(hc, f.location) - hash.hash_line(hc, f.server) - hash.hash_line(hc, tostring(f.unpack)) - hash.hash_line(hc, tostring(f.patch)) - hash.hash_line(hc, tostring(f.copy)) - end - src.sourceid = hash.hash_finish(hc) - return true, nil, src.sourceid -end - --- Create a source result containing the generated Makefile and files -- belonging to the source, for use with collect_project. -- Result refers to a collection of files to recreate an e2source for @@ -519,12 +658,12 @@ end function files.toresult(info, sourcename, sourceset, directory) local rc, re, out local e = err.new("converting result failed") - local s = info.sources[sourcename] + local src = source.sources[sourcename] local source = "source" -- directory to store source files in local makefile = e2lib.join(directory, "Makefile") out = { ".PHONY: place\n\nplace:\n" } - for _,file in ipairs(s.file) do + for file in src:file_iter() do e2lib.logf(4, "export file: %s", file.location) local destdir = string.format("%s/%s", directory, source) local destname = nil @@ -615,8 +754,8 @@ function files.toresult(info, sourcename, sourceset, directory) -- write licences local destdir = string.format("%s/licences", directory) local fname = string.format("%s/%s.licences", destdir, - e2lib.basename(file.location)) - local licence_list = table.concat(file.licences, "\n") .. "\n" + e2lib.basename(file.location)) + local licence_list = file.licences:concat_sorted("\n") .. "\n" rc, re = e2lib.mkdir_recursive(destdir) if not rc then return false, e:cat(re) diff --git a/plugins/git.lua b/plugins/git.lua index d8fc00f..5b6144e 100644 --- a/plugins/git.lua +++ b/plugins/git.lua @@ -30,6 +30,7 @@ local git = {} local cache = require("cache") +local class = require("class") local e2lib = require("e2lib") local e2option = require("e2option") local e2tool = require("e2tool") @@ -42,6 +43,7 @@ local scm = require("scm") local strict = require("strict") local tools = require("tools") local url = require("url") +local source = require("source") --- Initialize git plugin. -- @param ctx Plugin context. See plugin module. @@ -50,6 +52,11 @@ local url = require("url") local function git_plugin_init(ctx) local rc, re + rc, re = source.register_source_class("git", git.git_source) + if not rc then + return false, re + end + rc, re = scm.register("git", git) if not rc then return false, re @@ -75,6 +82,191 @@ plugin_descriptor = { exit = function (ctx) return true end, } +git.git_source = class("git_source", source.basic_source) + +function git.git_source:initialize(rawsrc) + assert(type(rawsrc) == "table") + assert(type(rawsrc.name) == "string" and rawsrc.name ~= "") + assert(type(rawsrc.type) == "string" and rawsrc.type ~= "") + + local rc, re + + source.basic_source.initialize(self, rawsrc) + + self._server = false + self._location = false + self._tag = false + self._branch = false + self._working = false + self._sourceids = { + ["working-copy"] = "working-copy", + } + self._commitids = {} + + rc, re = e2lib.vrfy_dict_exp_keys(rawsrc, "e2source", { + "branch", + "env", + "licences", + "location", + "name", + "server", + "tag", + "type", + "working", + }) + if not rc then + error(re) + end + + rc, re = source.generic_source_validate_licences(rawsrc, self) + if not rc then + error(re) + end + + rc, re = source.generic_source_validate_env(rawsrc, self) + if not rc then + error(re) + end + + rc, re = source.generic_source_validate_server(rawsrc, true) + if not rc then + error(re) + end + self._server = rawsrc.server + + rc, re = source.generic_source_validate_working(rawsrc) + if not rc then + error(re) + end + self._working = rawsrc.working + + for _,attr in ipairs({ "branch", "location", "tag" }) do + if rawsrc[attr] == nil then + error(err.new("source has no `%s' attribute", attr)) + elseif type(rawsrc[attr]) ~= "string" then + error(err.new("'%s' must be a string", attr)) + elseif rawsrc[attr] == "" then + error(err.new("'%s' may not be empty", attr)) + end + end + + self._branch = rawsrc.branch + self._location = rawsrc.location + self._tag = rawsrc.tag +end + +function git.git_source:get_server() + assert(type(self._server) == "string") + + return self._server +end + +function git.git_source:get_location() + assert(type(self._location) == "string") + + return self._location +end + +function git.git_source:get_working() + assert(type(self._working) == "string") + + return self._working +end + +function git.git_source:get_branch() + assert(type(self._branch) == "string") + + return self._branch +end + + +function git.git_source:get_tag() + assert(type(self._tag) == "string") + + return self._tag +end + +function git.git_source:sourceid(sourceset) + assert(type(sourceset) == "string" and #sourceset > 0, + "sourceset arg invalid") + + local rc, re, info, id, hc, licences + + if self._sourceids[sourceset] then + return self._sourceids[sourceset] + end + + info = e2tool.info() + assert(info) + + rc, re, id = git.git_commit_id(info, self._name, sourceset, + e2option.opts["check-remote"]) + if not rc then + return false, re + end + + hc = hash.hash_start() + hash.hash_line(hc, self._name) + hash.hash_line(hc, self._type) + hash.hash_line(hc, self._env:id()) + + licences = self:get_licences() + for licencename in licences:iter_sorted() do + local lid, re = licence.licences[licencename]:licenceid(info) + if not lid then + return false, re + end + hash.hash_line(hc, lid) + end + + hash.hash_line(hc, self._server) + hash.hash_line(hc, self._location) + hash.hash_line(hc, self._working) + hash.hash_line(hc, id) + self._commitids[sourceset] = id + self._sourceids[sourceset] = hash.hash_finish(hc) + + return self._sourceids[sourceset] +end + +function git.git_source:display() + local rev_tag, rev_branch, licences + + -- try to calculte the sourceid, but do not care if it fails. + -- working copy might be unavailable + self:sourceid("tag") + self:sourceid("branch") + + rev_tag = "" + rev_branch = "" + if self._commitids["tag"] then + rev_tag = string.format("[%s...]", self._commitids["tag"]:sub(1,8)) + end + if self._commitids["branch"] then + rev_branch = string.format("[%s...]", self._commitids["branch"]:sub(1,8)) + end + local d = {} + table.insert(d, string.format("type = %s", self:get_type())) + table.insert(d, string.format("branch = %-15s %s", self._branch, rev_branch)) + table.insert(d, string.format("tag = %-15s %s", self._tag, rev_tag)) + table.insert(d, string.format("server = %s", self._server)) + table.insert(d, string.format("location = %s", self._location)) + table.insert(d, string.format("working = %s", self._working)) + + licences = self:get_licences() + for licencename in licences:iter_sorted() do + table.insert(d, string.format("licence = %s", licencename)) + end + + for sourceset, sid in pairs(self._sourceids) do + if sid then + table.insert(d, string.format("sourceid [%s] = %s", sourceset, sid)) + end + end + + return d +end + --- Return the git commit ID of the specified source configuration. Specific to -- sources of type git, useful for writing plugins. -- @param info Info table. @@ -88,7 +280,7 @@ function git.git_commit_id(info, sourcename, sourceset, check_remote) local rc, re, e, src, id, fr, gitdir, ref e = err.new("getting commit ID failed for source: %s", sourcename) - src = info.sources[sourcename] + src = source.sources[sourcename] rc, re = scm.working_copy_available(info, sourcename) if not rc then @@ -100,17 +292,17 @@ function git.git_commit_id(info, sourcename, sourceset, check_remote) return false, e:cat(re) end - gitdir = e2lib.join(info.root, src.working, ".git") + gitdir = e2lib.join(info.root, src:get_working(), ".git") - if sourceset == "branch" or (sourceset == "lazytag" and src.tag == "^") then - ref = string.format("refs/heads/%s", src.branch) + if sourceset == "branch" or (sourceset == "lazytag" and src:get_tag() == "^") then + ref = string.format("refs/heads/%s", src:get_branch()) rc, re, id = generic_git.lookup_id(gitdir, false, ref) if not rc then return false, e:cat(re) end - elseif sourceset == "tag" or (sourceset == "lazytag" and src.tag ~= "^") then - ref = string.format("refs/tags/%s", src.tag) + elseif sourceset == "tag" or (sourceset == "lazytag" and src:get_tag() ~= "^") then + ref = string.format("refs/tags/%s", src:get_tag()) rc, re, id = generic_git.lookup_id(gitdir, false, ref) if not rc then @@ -118,7 +310,7 @@ function git.git_commit_id(info, sourcename, sourceset, check_remote) end if id and check_remote then - rc, re = generic_git.verify_remote_tag(gitdir, src.tag) + rc, re = generic_git.verify_remote_tag(gitdir, src:get_tag()) if not rc then return false, e:cat(re) end @@ -129,62 +321,13 @@ function git.git_commit_id(info, sourcename, sourceset, check_remote) if not id then re = err.new("can't get git commit ID for ref %q from repository %q", - ref, src.working) + ref, src:get_working()) return false, e:cat(re) end return true, nil, id end ---- validate source configuration, log errors to the debug log --- @param info the info table --- @param sourcename the source name --- @return bool --- @return an error object on error -function git.validate_source(info, sourcename) - local rc, re = scm.generic_source_validate(info, sourcename) - if not rc then - -- error in generic configuration. Don't try to go on. - return false, re - end - local src = info.sources[ sourcename ] - local e = err.new("in source %s:", sourcename) - rc, re = scm.generic_source_default_working(info, sourcename) - if not rc then - return false, e:cat(re) - end - e:setcount(0) - -- catch deprecated attributes - if src.remote then - e:append("source has deprecated `remote' attribute") - end - if not src.server then - e:append("source has no `server' attribute") - end - if src.server and (not cache.valid_server(info.cache, src.server)) then - e:append("invalid server: %s", src.server) - end - if not src.licences then - e:append("source has no `licences' attribute") - end - if not src.branch then - e:append("source has no `branch' attribute") - end - if type(src.tag) ~= "string" then - e:append("source has no `tag' attribute or tag attribute has wrong type") - end - if not src.location then - e:append("source has no `location' attribute") - end - if not src.working then - e:append("source has no `working' attribute") - end - if e:getcount() > 0 then - return false, e - end - return true, nil -end - --- update a working copy -- @param info the info structure -- @param sourcename string @@ -193,7 +336,7 @@ end function git.update(info, sourcename) local e, rc, re, src, gitwc, gitdir, argv, id, branch, remote - src = info.sources[sourcename] + src = source.sources[sourcename] e = err.new("updating source '%s' failed", sourcename) rc, re = scm.working_copy_available(info, sourcename) @@ -201,9 +344,9 @@ function git.update(info, sourcename) return false, e:cat(re) end - e2lib.logf(2, "updating %s [%s]", src.working, src.branch) + e2lib.logf(2, "updating %s [%s]", src:get_working(), src:get_branch()) - gitwc = e2lib.join(info.root, src.working) + gitwc = e2lib.join(info.root, src:get_working()) gitdir = e2lib.join(gitwc, ".git") argv = generic_git.git_new_argv(gitdir, gitwc, "fetch") @@ -234,20 +377,20 @@ function git.update(info, sourcename) return true end - if branch ~= "refs/heads/" .. src.branch then + if branch ~= "refs/heads/" .. src:get_branch() then e2lib.warnf("WOTHER", "not on configured branch. Skipping.") return true end rc, re, remote = generic_git.git_config( - gitdir, "branch."..src.branch.."remote") + gitdir, "branch."..src:get_branch().."remote") if not rc or string.len(remote) == 0 then e2lib.warnf("WOTHER", "no remote configured for branch %q. Skipping.", - src.branch) + src:get_branch()) return true end - branch = remote .. "/" .. src.branch + branch = remote .. "/" .. src:get_branch() argv = generic_git.git_new_argv(gitdir, gitwc, "merge", "--ff-only", branch) rc, re = generic_git.git(argv) if not rc then @@ -265,33 +408,34 @@ end function git.fetch_source(info, sourcename) local e, rc, re, src, git_dir, work_tree, id - src = info.sources[sourcename] + src = source.sources[sourcename] e = err.new("fetching source failed: %s", sourcename) - work_tree = e2lib.join(info.root, src.working) + work_tree = e2lib.join(info.root, src:get_working()) git_dir = e2lib.join(work_tree, ".git") - e2lib.logf(2, "cloning %s:%s [%s]", src.server, src.location, src.branch) + e2lib.logf(2, "cloning %s:%s [%s]", src:get_server(), src:get_location(), + src:get_branch()) - rc, re = generic_git.git_clone_from_server(info.cache, src.server, - src.location, work_tree, false --[[always checkout]]) + rc, re = generic_git.git_clone_from_server(info.cache, src:get_server(), + src:get_location(), work_tree, false --[[always checkout]]) if not rc then return false, e:cat(re) end rc, re, id = generic_git.lookup_id(git_dir, false, - "refs/heads/" .. src.branch) + "refs/heads/" .. src:get_branch()) if not rc then return false, e:cat(re) elseif not id then - rc, re = generic_git.git_branch_new1(work_tree, true, src.branch, - "origin/" .. src.branch) + rc, re = generic_git.git_branch_new1(work_tree, true, src:get_branch(), + "origin/" .. src:get_branch()) if not rc then return false, e:cat(re) end rc, re = generic_git.git_checkout1(work_tree, - "refs/heads/" .. src.branch) + "refs/heads/" .. src:get_branch()) if not rc then return false, e:cat(re) end @@ -308,16 +452,16 @@ end -- @return bool -- @return nil on success, an error string on error function git.prepare_source(info, sourcename, sourceset, buildpath) - local src = info.sources[ sourcename ] + local src = source.sources[sourcename] local rc, re, e local e = err.new("preparing git sources failed") rc, re = scm.generic_source_check(info, sourcename, true) if not rc then return false, e:cat(re) end - local gitdir = e2lib.join(info.root, src.working, ".git") + local gitdir = e2lib.join(info.root, src:get_working(), ".git") if sourceset == "branch" or - (sourceset == "lazytag" and src.tag == "^") then + (sourceset == "lazytag" and src:get_tag() == "^") then local argv, work_tree rc, re = git.git_commit_id(info, sourcename, sourceset) @@ -332,7 +476,7 @@ function git.prepare_source(info, sourcename, sourceset, buildpath) end argv = generic_git.git_new_argv(gitdir, work_tree, "checkout", "-f") - table.insert(argv, "refs/heads/" .. src.branch) + table.insert(argv, "refs/heads/" .. src:get_branch()) table.insert(argv, "--") rc, re = generic_git.git(argv) @@ -340,7 +484,7 @@ function git.prepare_source(info, sourcename, sourceset, buildpath) return false, e:cat(re) end elseif sourceset == "tag" or - (sourceset == "lazytag" and src.tag ~= "^") then + (sourceset == "lazytag" and src:get_tag() ~= "^") then local argv, work_tree rc, re = git.git_commit_id(info, sourcename, sourceset) @@ -355,7 +499,7 @@ function git.prepare_source(info, sourcename, sourceset, buildpath) end argv = generic_git.git_new_argv(gitdir, work_tree, "checkout", "-f") - table.insert(argv, "refs/tags/" .. src.tag) + table.insert(argv, "refs/tags/" .. src:get_tag()) table.insert(argv, "--") rc, re = generic_git.git(argv) @@ -365,7 +509,7 @@ function git.prepare_source(info, sourcename, sourceset, buildpath) elseif sourceset == "working-copy" then local working, destdir, empty - working = e2lib.join(info.root, src.working) + working = e2lib.join(info.root, src:get_working()) destdir = e2lib.join(buildpath, sourcename) rc, re = e2lib.mkdir_recursive(destdir) @@ -392,7 +536,7 @@ function git.prepare_source(info, sourcename, sourceset, buildpath) end if empty then - e2lib.warnf("WOTHER", "in result: %s", src.name) + e2lib.warnf("WOTHER", "in result: %s", sourcename) e2lib.warnf("WOTHER", "working copy seems empty") end else @@ -402,19 +546,17 @@ function git.prepare_source(info, sourcename, sourceset, buildpath) return true end ---- check if a working copy for a git repository is available +--- Check if a working copy for a git repository is available -- @param info the info structure -- @param sourcename string --- @return bool --- @return sometimes an error string, when ret. false. XXX interface cleanup. +-- @return True if available, false otherwise. function git.working_copy_available(info, sourcename) - local src = info.sources[sourcename] - local rc, re - local e = err.new("checking if working copy is available for source %s", - sourcename) - local gitwc = e2lib.join(info.root, src.working) - local rc = e2lib.isdir(gitwc) - return rc, nil + local rc + local src = source.sources[sourcename] + local gitwc = e2lib.join(info.root, src:get_working()) + + rc = e2lib.isdir(gitwc) + return rc end function git.has_working_copy(info, sname) @@ -450,13 +592,14 @@ end -- @return a table, nil on error -- @return an error string on failure function git.display(info, sourcename) - local src = info.sources[sourcename] + error("called git.display") + local src = source.sources[sourcename] local rc, re local e = err.new("display source information failed") -- try to calculte the sourceid, but do not care if it fails. -- working copy might be unavailable - scm.sourceid(info, sourcename, "tag") - scm.sourceid(info, sourcename, "branch") + src:sourceid("tag") + src:sourceid("branch") local rev_tag = "" local rev_branch = "" if src.commitid["tag"] then @@ -466,12 +609,12 @@ function git.display(info, sourcename) rev_branch = string.format("[%s...]", src.commitid["branch"]:sub(1,8)) end local display = {} - display[1] = string.format("type = %s", src.type) - display[2] = string.format("branch = %-15s %s", src.branch, rev_branch) - display[3] = string.format("tag = %-15s %s", src.tag, rev_tag) - display[4] = string.format("server = %s", src.server) - display[5] = string.format("location = %s", src.location) - display[6] = string.format("working = %s", src.working) + display[1] = string.format("type = %s", src:get_type()) + display[2] = string.format("branch = %-15s %s", src:get_branch(), rev_branch) + display[3] = string.format("tag = %-15s %s", src:get_tag(), rev_tag) + display[4] = string.format("server = %s", src:get_server()) + display[5] = string.format("location = %s", src:get_location()) + display[6] = string.format("working = %s", src:get_working()) local i = 8 for _,l in ipairs(src.licences) do display[i] = string.format("licence = %s", l) @@ -489,53 +632,6 @@ function git.display(info, sourcename) return display end ---- calculate an id for a source --- @param info --- @param sourcename --- @param sourceset --- @return string: the sourceid, or nil --- @return an error string -function git.sourceid(info, sourcename, sourceset) - local src = info.sources[sourcename] - local rc, re, e, id - if not src.sourceid then - src.sourceid = {} - src.sourceid["working-copy"] = "working-copy" - src.commitid = {} - end - if src.sourceid[sourceset] then - return true, nil, src.sourceid[sourceset] - end - - rc, re, id = git.git_commit_id(info, sourcename, sourceset, - e2option.opts["check-remote"]) - if not rc then - return false, re - end - - src.commitid[sourceset] = id - local hc = hash.hash_start() - hash.hash_line(hc, src.name) - hash.hash_line(hc, src.type) - hash.hash_line(hc, src._env:id()) - for _,ln in ipairs(src.licences) do - local lid, re = licence.licences[ln]:licenceid(info) - if not lid then - return false, re - end - hash.hash_line(hc, lid) - end - -- git specific - --hash.hash_line(hc, src.branch) - --hash.hash_line(hc, src.tag) - hash.hash_line(hc, src.server) - hash.hash_line(hc, src.location) - hash.hash_line(hc, src.working) - hash.hash_line(hc, src.commitid[sourceset]) - src.sourceid[sourceset] = hash.hash_finish(hc) - return true, nil, src.sourceid[sourceset] -end - function git.toresult(info, sourcename, sourceset, directory) local rc, re, argv local e = err.new("converting result") @@ -543,11 +639,11 @@ function git.toresult(info, sourcename, sourceset, directory) if not rc then return false, e:cat(re) end - local src = info.sources[sourcename] + local src = source.sources[sourcename] local makefile = "Makefile" local source = "source" local sourcedir = e2lib.join(directory, source) - local archive = string.format("%s.tar.gz", src.name) + local archive = string.format("%s.tar.gz", src:get_name()) local cmd = nil rc, re = e2lib.mkdir_recursive(sourcedir) @@ -558,7 +654,7 @@ function git.toresult(info, sourcename, sourceset, directory) if sourceset == "tag" or sourceset == "branch" then local ref, tmpfn - ref, re = generic_git.sourceset2ref(sourceset, src.branch, src.tag) + ref, re = generic_git.sourceset2ref(sourceset, src:get_branch(), src:get_tag()) if not ref then return false, e:cat(re) end @@ -568,7 +664,7 @@ function git.toresult(info, sourcename, sourceset, directory) return false, e:cat(re) end - argv = generic_git.git_new_argv(nil, e2lib.join(info.root, src.working)) + argv = generic_git.git_new_argv(nil, e2lib.join(info.root, src:get_working())) table.insert(argv, "archive") table.insert(argv, "--format=tar") -- older versions don't have "tar.gz" table.insert(argv, string.format("--prefix=%s/", sourcename)) @@ -592,7 +688,7 @@ function git.toresult(info, sourcename, sourceset, directory) end elseif sourceset == "working-copy" then argv = { - "-C", e2lib.join(info.root, src.working), + "-C", e2lib.join(info.root, src:get_working()), string.format("--transform=s,^./,./%s/,", sourcename), "--exclude=.git", "-czf", @@ -620,7 +716,8 @@ function git.toresult(info, sourcename, sourceset, directory) -- write licences local destdir = e2lib.join(directory, "licences") local fname = string.format("%s/%s.licences", destdir, archive) - local licence_list = table.concat(src.licences, "\n") .. "\n" + local licences = src:get_licences() + local licence_list = licences:concat_sorted("\n") .. "\n" rc, re = e2lib.mkdir_recursive(destdir) if not rc then return false, e:cat(re) @@ -644,24 +741,24 @@ function git.check_workingcopy(info, sourcename) end -- check if branch exists - local src = info.sources[sourcename] - local gitdir = e2lib.join(info.root, src.working, ".git") - local ref = string.format("refs/heads/%s", src.branch) + local src = source.sources[sourcename] + local gitdir = e2lib.join(info.root, src:get_working(), ".git") + local ref = string.format("refs/heads/%s", src:get_branch()) local id rc, re, id = generic_git.lookup_id(gitdir, false, ref) if not rc then return false, e:cat(re) elseif not id then - return false, e:cat(err.new("branch %q does not exist", src.branch)) + return false, e:cat(err.new("branch %q does not exist", src:get_branch())) end -- git config branch..remote == "origin" local query, expect, res - query = string.format("branch.%s.remote", src.branch) + query = string.format("branch.%s.remote", src:get_branch()) res, re = generic_git.git_config(gitdir, query) if not res then - e:append("remote is not configured for branch \"%s\"", src.branch) + e:append("remote is not configured for branch \"%s\"", src:get_branch()) return false, e elseif res ~= "origin" then e:append("%s is not \"origin\"", query) @@ -670,7 +767,7 @@ function git.check_workingcopy(info, sourcename) -- git config remote.origin.url == server:location query = string.format("remote.origin.url") - expect, re = git_url(info.cache, src.server, src.location) + expect, re = git_url(info.cache, src:get_server(), src:get_location()) if not expect then return false, e:cat(re) end diff --git a/plugins/svn.lua b/plugins/svn.lua index 9ab8771..b914e44 100644 --- a/plugins/svn.lua +++ b/plugins/svn.lua @@ -30,7 +30,9 @@ local svn = {} local cache = require("cache") +local class = require("class") local e2lib = require("e2lib") +local e2tool = require("e2tool") local eio = require("eio") local err = require("err") local hash = require("hash") @@ -39,13 +41,30 @@ local scm = require("scm") local strict = require("strict") local tools = require("tools") local url = require("url") +local source = require("source") plugin_descriptor = { description = "SVN SCM Plugin", - init = function (ctx) scm.register("svn", svn) return true end, + init = function (ctx) + local rc, re + + rc, re = source.register_source_class("svn", svn.svn_source) + if not rc then + return false, re + end + + rc, re = scm.register("svn", svn) + if not rc then + return false, re + end + + return true + end, exit = function (ctx) return true end, } +svn.svn_source = class("svn_source", source.basic_source) + --- translate url into subversion url -- @param u table: url table -- @return string: subversion style url @@ -143,12 +162,222 @@ local function svn_tool(argv, workdir) return true, nil, table.concat(out) end +function svn.svn_source:initialize(rawsrc) + assert(type(rawsrc) == "table") + assert(type(rawsrc.name) == "string" and #rawsrc.name > 0) + assert(type(rawsrc.type) == "string" and rawsrc.type == "svn") + + local rc, re + + source.basic_source.initialize(self, rawsrc) + + self._server = false + self._location = false + self._tag = false + self._branch = false + self._working = false + self._workingcopy_subdir = false + self._sourceids = { + ["working-copy"] = "working-copy" + } + + rc, re = e2lib.vrfy_dict_exp_keys(rawsrc, "e2source", { + "branch", + "env", + "licences", + "location", + "name", + "server", + "tag", + "type", + "working", + "workingcopy_subdir", + }) + + if not rc then + error(re) + end + + rc, re = source.generic_source_validate_licences(rawsrc, self) + if not rc then + error(re) + end + + rc, re = source.generic_source_validate_env(rawsrc, self) + if not rc then + error(re) + end + + rc, re = source.generic_source_validate_server(rawsrc, true) + if not rc then + error(re) + end + self._server = rawsrc.server + + rc, re = source.generic_source_validate_working(rawsrc) + if not rc then + error(re) + end + self._working = rawsrc.working + + -- workingcopy_subdir is optional and defaults to the branch + -- make sure branch is checked first to avoid confusing error + if rawsrc.workingcopy_subdir == nil then + rawsrc.workingcopy_subdir = rawsrc.branch + end + + for _,attr in ipairs({"branch", "location", "tag", "workingcopy_subdir"}) do + if rawsrc[attr] == nil then + error(err.new("source has no `%s' attribute", attr)) + elseif type(rawsrc[attr]) ~= "string" then + error(err.new("'%s' must be a string", attr)) + elseif rawsrc[attr] == "" then + error(err.new("'%s' may not be empty", attr)) + end + end + + self._branch = rawsrc.branch + self._location = rawsrc.location + self._tag = rawsrc.tag + self._workingcopy_subdir = rawsrc.workingcopy_subdir +end + +function svn.svn_source:get_working() + assert(type(self._working) == "string") + return self._working +end + +function svn.svn_source:get_workingcopy_subdir() + assert(type(self._workingcopy_subdir) == "string") + return self._workingcopy_subdir +end + +function svn.svn_source:get_server() + assert(type(self._server) == "string") + return self._server +end + +function svn.svn_source:get_location() + assert(type(self._location) == "string") + return self._location +end + +function svn.svn_source:get_branch() + assert(type(self._branch) == "string") + return self._branch +end + +function svn.svn_source:get_tag() + assert(type(self._tag) == "string") + return self._tag +end + +function svn.svn_source:sourceid(sourceset) + assert(type(sourceset) == "string" and #sourceset > 0) + + local rc, re + local hc, surl, svnurl, argv, out, svnrev, lid, svnrev, info, licences + + if self._sourceids[sourceset] then + return self._sourceids[sourceset] + end + + hc = hash.hash_start() + hash.hash_line(hc, self._name) + hash.hash_line(hc, self._type) + hash.hash_line(hc, self._env:id()) + + info = e2tool.info() + assert(type(info) == "table") + + licences = self:get_licences() + for licencename in licences:iter_sorted() do + lid, re = licence.licences[licencename]:licenceid(info) + if not lid then + return false, re + end + hash.hash_line(hc, lid) + end + + surl, re = cache.remote_url(info.cache, self._server, self._location) + if not surl then + return false, re + end + + svnurl, re = mksvnurl(surl) + if not svnurl then + return false, re + end + + hash.hash_line(hc, self._server) + hash.hash_line(hc, self._location) + + if sourceset == "tag" then + hash.hash_line(hc, self._tag) + argv = { "info", svnurl.."/"..self._tag } + elseif sourceset == "branch" then + hash.hash_line(hc, self._branch) + argv = { "info", svnurl.."/"..self._branch } + elseif sourceset == "lazytag" then + return false, err.new("svn source does not support lazytag mode") + else + return false, + err.new("svn sourceid can't handle sourceset %q", sourceset) + end + + rc, re, out = svn_tool(argv) + if not rc then + return false, + err.new("retrieving revision for tag or branch failed"):cat(re) + end + + svnrev = string.match(out, "Last Changed Rev: (%d+)") + if not svnrev or string.len(svnrev) == 0 then + return false, err.new("could not find SVN revision") + end + hash.hash_line(hc, svnrev) + + self._sourceids[sourceset] = hash.hash_finish(hc) + + return self._sourceids[sourceset] +end + +function svn.svn_source:display() + local d, licences + + -- try to calculte the sourceid, but do not care if it fails. + -- working copy might be unavailable + self:sourceid("tag") + self:sourceid("branch") + + d = {} + table.insert(d, string.format("type = %s", self:get_type())) + table.insert(d, string.format("server = %s", self._server)) + table.insert(d, string.format("remote = %s", self._location)) + table.insert(d, string.format("branch = %s", self._branch)) + table.insert(d, string.format("tag = %s", self._tag)) + table.insert(d, string.format("working = %s", self._working)) + + licences = self:get_licences() + for licencename in licences:iter_sorted() do + table.insert(d, string.format("licence = %s", licencename)) + end + + for sourceset, sid in pairs(self._sourceids) do + if sid then + table.insert(d, string.format("sourceid [%s] = %s", sourceset, sid)) + end + end + + return d +end + function svn.fetch_source(info, sourcename) local rc, re local e = err.new("fetching source failed: %s", sourcename) - local src = info.sources[sourcename] - local location = src.location - local server = src.server + local src = source.sources[sourcename] + local location = src:get_location() + local server = src:get_server() local surl, re = cache.remote_url(info.cache, server, location) if not surl then return false, e:cat(re) @@ -158,21 +387,21 @@ function svn.fetch_source(info, sourcename) return false, e:cat(re) end - local argv = { "checkout", svnurl, info.root .. "/" .. src.working } + local argv = { "checkout", svnurl, info.root .. "/" .. src:get_working() } rc, re = svn_tool(argv) if not rc then return false, e:cat(re) end - return true, nil + return true end -function svn.prepare_source(info, sourcename, source_set, build_path) +function svn.prepare_source(info, sourcename, sourceset, build_path) local rc, re local e = err.new("svn.prepare_source failed") - local src = info.sources[ sourcename ] - local location = src.location - local server = src.server + local src = source.sources[sourcename] + local location = src:get_location() + local server = src:get_server() local surl, re = cache.remote_url(info.cache, server, location) if not surl then return false, e:cat(re) @@ -181,12 +410,12 @@ function svn.prepare_source(info, sourcename, source_set, build_path) if not svnurl then return false, e:cat(re) end - if source_set == "tag" or source_set == "branch" then + if sourceset == "tag" or sourceset == "branch" then local rev - if source_set == "tag" then - rev = src.tag - else -- source_set == "branch" - rev = src.branch + if sourceset == "tag" then + rev = src:get_tag() + else -- sourceset == "branch" + rev = src:get_branch() end local argv = { "export", svnurl .. "/" .. rev, build_path .. "/" .. sourcename } @@ -194,10 +423,11 @@ function svn.prepare_source(info, sourcename, source_set, build_path) if not rc then return false, e:cat(re) end - elseif source_set == "working-copy" then + elseif sourceset == "working-copy" then -- cp -R info.root/src.working/src.workingcopy_subdir build_path - local s = e2lib.join(info.root, src.working, src.workingcopy_subdir) - local d = e2lib.join(build_path, src.name) + local s = e2lib.join(info.root, src:get_working(), + src:get_workingcopy_subdir()) + local d = e2lib.join(build_path, src:get_name()) rc, re = e2lib.cp(s, d, true) if not rc then return false, e:cat(re) @@ -210,8 +440,9 @@ end function svn.working_copy_available(info, sourcename) local rc, re - local src = info.sources[sourcename] - local dir = e2lib.join(info.root, src.working) + local src = source.sources[sourcename] + + local dir = e2lib.join(info.root, src:get_working()) return e2lib.isdir(dir) end @@ -220,127 +451,30 @@ function svn.check_workingcopy(info, sourcename) local e = err.new("checking working copy failed") e:append("in source %s (svn configuration):", sourcename) e:setcount(0) - local src = info.sources[sourcename] + local src = source.sources[sourcename] if e:getcount() > 0 then return false, e end -- check if the configured branch and tag exist local d - d = e2lib.join(info.root, src.working, src.branch) + d = e2lib.join(info.root, src:get_working(), src:get_branch()) if not e2lib.isdir(d) then - e:append("branch does not exist: %s", src.branch) + e:append("branch does not exist: %s", src:get_branch()) end - d = e2lib.join(info.root, src.working, src.tag) + d = e2lib.join(info.root, src:get_working(), src:get_tag()) if not e2lib.isdir(d) then - e:append("tag does not exist: %s", src.tag) + e:append("tag does not exist: %s", src:get_tag()) end if e:getcount() > 0 then return false, e end - return true, nil + return true end function svn.has_working_copy(info, sname) return true end ---- create a table of lines for display --- @param info the info structure --- @param sourcename string --- @return a table, nil on error --- @return an error string on failure -function svn.display(info, sourcename) - local src = info.sources[sourcename] - local rc, e - local display = {} - display[1] = string.format("type = %s", src.type) - display[2] = string.format("server = %s", src.server) - display[3] = string.format("remote = %s", src.location) - display[4] = string.format("branch = %s", src.branch) - display[5] = string.format("tag = %s", src.tag) - display[6] = string.format("working = %s", src.working) - local i = 7 - for _,l in pairs(src.licences) do - display[i] = string.format("licence = %s", l) - i = i + 1 - end - return display -end - ---- calculate an id for a source --- @param info --- @param sourcename --- @param source_set -function svn.sourceid(info, sourcename, source_set) - local src = info.sources[sourcename] - local rc, re - local hc, surl, svnurl, argv, out, svnrev, lid - - if not src.sourceid then - src.sourceid = {} - end - - src.sourceid["working-copy"] = "working-copy" - if src.sourceid[source_set] then - return true, nil, src.sourceid[source_set] - end - - hc = hash.hash_start() - hash.hash_line(hc, src.name) - hash.hash_line(hc, src.type) - hash.hash_line(hc, src._env:id()) - for _,ln in pairs(src.licences) do - lid, re = licence.licences[ln]:licenceid(info) - if not lid then - return false, re - end - hash.hash_line(hc, lid) - end - - -- svn specific - surl, re = cache.remote_url(info.cache, src.server, src.location) - if not surl then - return false, re - end - - svnurl, re = mksvnurl(surl) - if not svnurl then - return false, re - end - - hash.hash_line(hc, src.server) - hash.hash_line(hc, src.location) - - if source_set == "tag" then - hash.hash_line(hc, src.tag) - argv = { "info", svnurl.."/"..src.tag } - elseif source_set == "branch" then - hash.hash_line(hc, src.branch) - argv = { "info", svnurl.."/"..src.branch } - elseif source_set == "lazytag" then - return false, err.new("svn source does not support lazytag mode") - else - return false, - err.new("svn sourceid can't handle source_set %q", source_set) - end - - rc, re, out = svn_tool(argv) - if not rc then - return false, - err.new("retrieving revision for tag or branch failed"):cat(re) - end - - svnrev = string.match(out, "Last Changed Rev: (%d+)") - if not svnrev or string.len(svnrev) == 0 then - return false, err.new("could not find SVN revision") - end - hash.hash_line(hc, svnrev) - - src.sourceid[source_set] = hash.hash_finish(hc) - - return true, nil, src.sourceid[source_set] -end - function svn.toresult(info, sourcename, sourceset, directory) -- /source/.tar.gz -- /makefile @@ -351,7 +485,7 @@ function svn.toresult(info, sourcename, sourceset, directory) if not rc then return false, e:cat(re) end - local src = info.sources[sourcename] + local src = source.sources[sourcename] -- write makefile local makefile = "Makefile" local source = "source" @@ -378,7 +512,7 @@ function svn.toresult(info, sourcename, sourceset, directory) return false, e:cat(re) end -- create a tarball in the final location - local archive = string.format("%s.tar.gz", src.name) + local archive = string.format("%s.tar.gz", src:get_name()) rc, re = e2lib.tar({ "-C", tmpdir ,"-czf", sourcedir .. "/" .. archive, sourcename }) if not rc then @@ -387,7 +521,8 @@ function svn.toresult(info, sourcename, sourceset, directory) -- write licences local destdir = e2lib.join(directory, "licences") local fname = string.format("%s/%s.licences", destdir, archive) - local licence_list = table.concat(src.licences, "\n") .. "\n" + local licences = src:get_licences() + local licence_list = licences:concat_sorted("\n") .. "\n" rc, re = e2lib.mkdir_recursive(destdir) if not rc then return false, e:cat(re) @@ -403,8 +538,8 @@ end function svn.update(info, sourcename) local rc, re local e = err.new("updating source '%s' failed", sourcename) - local src = info.sources[ sourcename ] - local workdir = e2lib.join(info.root, src.working) + local src = source.sources[sourcename] + local workdir = e2lib.join(info.root, src:get_working()) rc, re = svn_tool({ "update" }, workdir) if not rc then return false, e:cat(re) @@ -412,63 +547,6 @@ function svn.update(info, sourcename) return true end ---- validate source configuration, log errors to the debug log --- @param info the info table --- @param sourcename the source name --- @return bool -function svn.validate_source(info, sourcename) - local rc, re = scm.generic_source_validate(info, sourcename) - if not rc then - -- error in generic configuration. Don't try to go on. - return false, re - end - local src = info.sources[ sourcename ] - if not src.sourceid then - src.sourceid = {} - end - local e = err.new("in source %s:", sourcename) - rc, re = scm.generic_source_default_working(info, sourcename) - if not rc then - return false, e:cat(re) - end - e:setcount(0) - if not src.server then - e:append("source has no `server' attribute") - end - if not src.licences then - e:append("source has no `licences' attribute") - end - if not src.location then - e:append("source has no `location' attribute") - end - if src.remote then - e:append("source has `remote' attribute, not allowed for svn sources") - end - if not src.branch then - e:append("source has no `branch' attribute") - end - if type(src.tag) ~= "string" then - e:append("source has no `tag' attribute or tag attribute has wrong type") - end - if type(src.workingcopy_subdir) ~= "string" then - e2lib.warnf("WDEFAULT", "in source %s", sourcename) - e2lib.warnf("WDEFAULT", - " workingcopy_subdir defaults to the branch: %s", src.branch) - src.workingcopy_subdir = src.branch - end - if not src.working then - e:append("source has no `working' attribute") - end - local rc, re = tools.check_tool("svn") - if not rc then - e:cat(re) - end - if e:getcount() > 0 then - return false, e - end - return true, nil -end - strict.lock(svn) -- vim:sw=4:sts=4:et: -- 2.39.5