]> git.e2factory.org Git - e2factory.git/commitdiff
e2project: introduce a class for the local project
authorTobias Ulmer <tu@emlix.com>
Mon, 24 Jul 2017 13:33:25 +0000 (15:33 +0200)
committerTobias Ulmer <tu@emlix.com>
Mon, 10 Dec 2018 17:00:11 +0000 (18:00 +0100)
Signed-off-by: Tobias Ulmer <tu@emlix.com>
12 files changed:
local/e2-build-numbers.lua
local/e2-build.lua
local/e2-cf.lua
local/e2-dlist.lua
local/e2-dsort.lua
local/e2-fetch-sources.lua
local/e2-help.lua
local/e2-ls-project.lua
local/e2-new-source.lua
local/e2-playground.lua
local/e2build.lua
local/e2tool.lua

index 98419d482abb9e8d718b5be27dd3444c61490b31..4d1da1156a7427b378b39a8305bf3f243aedf4fe 100644 (file)
@@ -29,10 +29,7 @@ local function e2_build_numbers(arg)
         error(re)
     end
 
-    rc, re = e2tool.local_init(nil, "build-numbers")
-    if not rc then
-        error(re)
-    end
+    e2tool.e2project():init_project("build-numbers")
 
     error(err.new("e2-build-numbers is deprecated and has been removed"))
 end
index 3737324c3bac7729c6c136e9ca000366f47d0e5b..3cf24f297c2ce11deb64eee1e553d593d312588b 100644 (file)
@@ -30,15 +30,14 @@ local project = require("project")
 local result = require("result")
 
 local function e2_build(arg)
+    local e2project
     local rc, re = e2lib.init()
     if not rc then
         error(re)
     end
 
-    local info, re = e2tool.local_init(nil, "build")
-    if not info then
-        error(re)
-    end
+    e2project = e2tool.e2project()
+    e2project:init_project("build")
 
     -- list of results with a specific build mode
     local wc_mode_results = {}
@@ -101,8 +100,8 @@ local function e2_build(arg)
         error(re)
     end
 
-    info, re = e2tool.collect_project_info(info)
-    if not info then
+    rc, re = e2project:load_project()
+    if not rc then
         error(re)
     end
 
index 6c38aea30aa6ae09399aff2bd90cfa593808e88e..cdc88fe14ff3f37e567cbc52a6e30fc2c5334a6f 100644 (file)
@@ -110,6 +110,7 @@ local function newsource(info, ...)
     local t = ...
     local name = t[2]
     local scm = t[3]
+    local e2project = e2tool.e2project()
 
     if not name then
         e:append("missing parameter: name")
@@ -127,7 +128,7 @@ local function newsource(info, ...)
     end
 
     local cftemplate =
-        e2lib.join(info.local_template_path, string.format("source.%s", scm))
+        e2lib.join(e2project:local_template_path(), string.format("source.%s", scm))
     if not e2lib.isfile(cftemplate) then
         return false, e:append("no template for '%s' available", scm)
     end
@@ -194,6 +195,7 @@ local function newresult(info, ...)
     local e = err.new("making new result failed")
     local t = ...
     local name = t[2]
+    local e2project = e2tool.e2project()
     if not name then
         return false, e:append("missing parameter: name")
     end
@@ -208,8 +210,8 @@ local function newresult(info, ...)
     local cf = e2tool.resultconfig(pathname, e2tool.root())
     local bs = e2tool.resultbuildscript(pathname, e2tool.root())
 
-    local cftemplate = e2lib.join(info.local_template_path, "result")
-    local bstemplate = e2lib.join(info.local_template_path, "build-script")
+    local cftemplate = e2lib.join(e2project:local_template_path(), "result")
+    local bstemplate = e2lib.join(e2project:local_template_path(), "build-script")
     if not e2lib.isfile(cftemplate) then
         return false, e:append("config template %s not available", cftemplate)
     end
@@ -307,25 +309,23 @@ local function editbuildscript(info, ...)
 end
 
 local function e2_cf(arg)
+    local e2project
     local rc, re = e2lib.init()
     if not rc then
         error(re)
     end
 
-    local info, re = e2tool.local_init(nil, "cf")
-    if not info then
-        error(re)
-    end
+    e2project = e2tool.e2project()
+    e2project:init_project("cf")
 
     local opts, arguments = e2option.parse(arg)
     if not opts then
         error(arguments)
     end
 
-    -- initialize some basics in the info structure without actually loading
-    -- the project configuration.
-    info, re = e2tool.collect_project_info(info, true)
-    if not info then
+    -- initialize some basics without loading the project configuration.
+    rc, re = e2project:load_project(true)
+    if not rc then
         error(re)
     end
 
@@ -365,7 +365,7 @@ local function e2_cf(arg)
             table.insert(a, o)
         end
         local f = commands[match[1]]
-        rc, re = f(info, a)
+        rc, re = f(e2project:info(), a)
         if not rc then
             error(re)
         end
index 6fd1b716b519390f5ab01b3b4bad4b3eb84fc31d..7efba725d35115b98dce0ec7fb5a404bb63b2d29 100644 (file)
@@ -26,15 +26,14 @@ local err = require("err")
 local result = require("result")
 
 local function e2_dlist(arg)
+    local e2project
     local rc, re = e2lib.init()
     if not rc then
         error(re)
     end
 
-    local info, re = e2tool.local_init(nil, "dlist")
-    if not info then
-        error(re)
-    end
+    e2project = e2tool.e2project()
+    e2project:init_project("dlist")
 
     e2option.flag("recursive", "show indirect dependencies, too")
     local opts, arguments = e2option.parse(arg)
@@ -45,11 +44,13 @@ local function e2_dlist(arg)
     if #arguments == 0 then
         error(err.new(
             "no result given - enter `e2-dlist --help' for usage information"))
-    elseif #arguments ~= 1 then e2option.usage(1) end
+    elseif #arguments ~= 1 then
+        e2option.usage(1)
+    end
 
     local resultname = arguments[1]
-    info, re = e2tool.collect_project_info(info)
-    if not info then
+    rc, re = e2project:load_project()
+    if not rc then
         error(re)
     end
 
index 82a28c53a358419b5b2fd2d15b11480d9b9b8ee8..b582e4b7b30364090d474f5e1fed6c556814a3d7 100644 (file)
@@ -24,23 +24,22 @@ local e2tool = require("e2tool")
 local e2option = require("e2option")
 
 local function e2_dsort(arg)
+    local e2project
     local rc, re = e2lib.init()
     if not rc then
         error(re)
     end
 
-    local info, re = e2tool.local_init(nil, "dsort")
-    if not info then
-        error(re)
-    end
+    e2project = e2tool.e2project()
+    e2project:init_project("dsort")
 
     local opts, re = e2option.parse(arg)
     if not opts then
         error(re)
     end
 
-    info, re = e2tool.collect_project_info(info)
-    if not info then
+    rc, re = e2project:load_project()
+    if not rc then
         error(re)
     end
 
index 9011b35babf1da3309e904b1acad04d785687a45..93db67881deab6cc03a2925a2675d1ac87ef8810 100644 (file)
@@ -28,15 +28,14 @@ local result = require("result")
 local source = require("source")
 
 local function e2_fetch_source(arg)
+    local e2project
     local rc, re = e2lib.init()
     if not rc then
         error(re)
     end
 
-    local info, re = e2tool.local_init(nil, "fetch-sources")
-    if not info then
-        error(re)
-    end
+    e2project = e2tool.e2project()
+    e2project:init_project("fetch-sources")
 
     local e = err.new()
 
@@ -53,8 +52,8 @@ local function e2_fetch_source(arg)
         error(arguments)
     end
 
-    info, re = e2tool.collect_project_info(info)
-    if not info then
+    rc, re = e2project:load_project()
+    if not rc then
         error(re)
     end
 
@@ -202,7 +201,7 @@ local function e2_fetch_source(arg)
 
     if next(sel) ~= nil then
         e2lib.log(2, "fetching sources...")
-        local rc, re = fetch_sources(info, opts, sel)
+        local rc, re = fetch_sources(e2project:info(), opts, sel)
         if not rc then
             e:cat(re)
         end
index cc35f6fa177e388cac3eca4d1af8d97d0becb5e3..74a7d91ba3cf488c12b164e2fbf9e4172339b513 100644 (file)
@@ -221,23 +221,22 @@ end
 -- @return Error object on failure.
 local function e2_help(arg)
     local rc, re, e
+    local e2project
     rc, re = e2lib.init()
     if not rc then
         error(re)
     end
 
-    local info, re = e2tool.local_init(nil, "help")
-    if not info then
-        error(re)
-    end
+    e2project = e2tool.e2project()
+    e2project:init_project("help")
 
     local opts, arguments = e2option.parse(arg)
     if not opts then
         error(arguments)
     end
 
-    info, re = e2tool.collect_project_info(info, true)
-    if not info then
+    rc, re = e2project:load_project(true)
+    if not rc then
         error(re)
     end
 
index ef530374a24aa2f1bd18ff0bf4f77b167fbe40dd..f2df89ddc0dfdb34336f11f3706ed4a85a8fec7a 100644 (file)
@@ -89,15 +89,14 @@ end
 -- @return Always true.
 -- @raise error, assert
 local function e2_ls_project(arg)
+    local e2project
     local rc, re = e2lib.init()
     if not rc then
         error(re)
     end
 
-    local info, re = e2tool.local_init(nil, "ls-project")
-    if not info then
-        error(re)
-    end
+    e2project = e2tool.e2project()
+    e2project:init_project("ls-project")
 
     policy.register_commandline_options()
     e2option.flag("dot", "generate dot(1) graph")
@@ -119,8 +118,8 @@ local function e2_ls_project(arg)
         error(re)
     end
 
-    info, re = e2tool.collect_project_info(info)
-    if not info then
+    rc, re = e2project:load_project()
+    if not rc then
         error(re)
     end
 
index 68a0a6c459a68c0bfbdb9c98ce11b2d52d49e0fa..70db61505cd1b6a029e6ec737f56cdff4c218d7a 100644 (file)
@@ -222,15 +222,14 @@ local function new_files_source(c, server, location, source_file, checksum_file,
 end
 
 local function e2_new_source(arg)
+    local e2project
     local rc, re = e2lib.init()
     if not rc then
         error(re)
     end
 
-    local info, re = e2tool.local_init(nil, "new-source")
-    if not info then
-        error(re)
-    end
+    e2project = e2tool.e2project()
+    e2project:init_project("new-source")
 
     e2option.flag("git", "create a git repository")
     e2option.flag("files", "create a new file on a files server")
@@ -241,8 +240,8 @@ local function e2_new_source(arg)
         error(arguments)
     end
 
-    info, re = e2tool.collect_project_info(info)
-    if not info then
+    rc, re = e2project:load_project()
+    if not rc then
         error(re)
     end
 
@@ -256,7 +255,8 @@ local function e2_new_source(arg)
             rserver = opts["server"]
         end
         local name = arguments[1]
-        local rlocation = string.format("%s/git/%s.git", info.project_location, name)
+        local rlocation = string.format("%s/git/%s.git",
+            e2project:project_location(), name)
         -- local
         local lserver = cache.server_names().dot
         local llocation = string.format("in/%s/.git", name)
index dbf6fb777724131c648525ff47d5416508681549..67b2fd8f673725a2bef9eb12b550e3aa0f0176cc 100644 (file)
@@ -29,15 +29,14 @@ local policy = require("policy")
 local result = require("result")
 
 local function e2_playground(arg)
+    local e2project
     local rc, re = e2lib.init()
     if not rc then
         error(re)
     end
 
-    local info, re = e2tool.local_init(nil, "playground")
-    if not info then
-        error(re)
-    end
+    e2project = e2tool.e2project()
+    e2project:init_project("playground")
 
     local e = err.new("entering playground failed")
     local rc, re
@@ -56,8 +55,9 @@ local function e2_playground(arg)
     if not build_mode then
         error(re)
     end
-    info, re = e2tool.collect_project_info(info)
-    if not info then
+
+    rc, re = e2project:load_project()
+    if not rc then
         error(re)
     end
 
index bfc3fe8161d57faac5513724d63bff785aac3107..69bae44d2a3f88e102d351f3b94f7d5c3e40dd0a 100644 (file)
@@ -271,7 +271,7 @@ function e2build.build_process_class:_result_available(res, return_flags)
     local buildid, sbid
     local e = err.new("error while checking if result is available: %s", res:get_name())
     local columns = tonumber(e2lib.globals.osenv["COLUMNS"])
-    local info = e2tool.info()
+    local e2project = e2tool.e2project()
 
     buildid, re = res:buildid()
     if not buildid then
@@ -297,7 +297,8 @@ function e2build.build_process_class:_result_available(res, return_flags)
     end
 
     local server, location =
-        res:build_mode().storage(info.project_location, project.release_id())
+        res:build_mode().storage(
+            e2project:project_location(), project.release_id())
     local result_location = e2lib.join(location, res:get_name(),
         buildid, "result.tar")
 
@@ -598,12 +599,12 @@ end
 ---
 function e2build.build_process_class:helper_unpack_result(res, dep, destdir)
     local rc, re, e
-    local info, buildid, server, location, resulttarpath, tmpdir
-    local path, resdir, dt, filesdir
+    local buildid, server, location, resulttarpath, tmpdir
+    local path, resdir, dt, filesdir, e2project
 
     e = err.new("unpacking result failed: %s", dep:get_name())
 
-    info = e2tool.info()
+    e2project = e2tool.e2project()
 
     buildid, re = dep:buildid()
     if not buildid then
@@ -611,7 +612,7 @@ function e2build.build_process_class:helper_unpack_result(res, dep, destdir)
     end
 
     server, location =
-        dep:build_mode().storage(info.project_location, project.release_id())
+        dep:build_mode().storage(e2project:project_location(), project.release_id())
 
     e2lib.logf(3, "searching for dependency %s in %s:%s",
         dep:get_name(), server, location)
@@ -833,7 +834,7 @@ function e2build.build_process_class:helper_deploy(res, tmpdir)
     -- result/files/*
     --   -> releases:<project>/<archive>/<release_id>/<result>/files/*
     --]]
-    local info = e2tool.info()
+    local e2project = e2tool.e2project()
     if not res:build_mode().deploy then
         e2lib.log(4, "deployment disabled for this build mode")
         return true
@@ -857,7 +858,7 @@ function e2build.build_process_class:helper_deploy(res, tmpdir)
     end
     table.insert(files, "checksums")
     local server, location = res:build_mode().deploy_storage(
-        info.project_location, project.release_id())
+        e2project:project_location(), project.release_id())
 
     -- do not re-deploy if this release was already done earlier
     local location1 = e2lib.join(location, res:get_name(), "checksums")
@@ -905,9 +906,9 @@ function e2build.build_process_class:_store_result(res, return_flags)
     local rc, re
     local e = err.new("fetching build results from chroot")
     local dt
-    local info
+    local e2project
 
-    info = e2tool.info()
+    e2project = e2tool.e2project()
 
     -- create a temporary directory to build up the result
     local tmpdir, re = e2lib.mktempdir()
@@ -1009,8 +1010,8 @@ function e2build.build_process_class:_store_result(res, return_flags)
         return false, e:cat(re)
     end
 
-    local server, location = res:build_mode().storage(info.project_location,
-        project.release_id())
+    local server, location = res:build_mode().storage(
+        e2project:project_location(), project.release_id())
 
     local buildid, re = res:buildid()
     if not buildid then
@@ -1039,12 +1040,13 @@ end
 ---
 function e2build.build_process_class:_linklast(res, return_flags)
     local rc, re, e
-    local info, server, location, buildid, dst, lnk
+    local server, location, buildid, dst, lnk, e2project
 
     e = err.new("creating link to last results")
-    info = e2tool.info()
+    e2project = e2tool.e2project()
     -- calculate the path to the result
-    server, location = res:build_mode().storage(info.project_location, project.release_id())
+    server, location = res:build_mode().storage(
+        e2project:project_location(), project.release_id())
 
     -- compute the "last" link/directory
     buildid, re = res:buildid()
index fda1dcb14446d62b5b05acd7601ac9f0fe5e3878..f37ccf4d5749eec38ba3976050643dfcf9faa9ac 100644 (file)
@@ -612,156 +612,6 @@ end
 
 --- @section end
 
---- Info table contains servers, caches and more...
--- @table info
--- @field startup_cwd Current working dir at startup (string).
--- @field chroot_umask Umask setting for chroot (decimal number).
--- @field host_umask Default umask of the process (decimal number).
--- @field project_location string: project location relative to the servers
--- @field local_template_path Path to the local templates (string).
-local _info = false
-
---- Open debug logfile.
--- @return True on success, false on error.
--- @return Error object on failure.
-local function opendebuglogfile()
-    local rc, re, e, logfile, debuglogfile
-
-    rc, re = e2lib.mkdir_recursive(e2lib.join(e2tool.root(), "log"))
-    if not rc then
-        e = err.new("error making log directory")
-        return false, e:cat(re)
-    end
-    logfile = e2lib.join(e2tool.root(), "log/debug.log")
-    rc, re = e2lib.rotate_log(logfile)
-    if not rc then
-        return false, re
-    end
-
-    debuglogfile, re = eio.fopen(logfile, "w")
-    if not debuglogfile then
-        e = err.new("error opening debug logfile")
-        return false, e:cat(re)
-    end
-
-    e2lib.globals.debuglogfile = debuglogfile
-
-    return true
-end
-
--- set the umask value to be used in chroot
-local _chroot_umask = 18 -- 022 octal
-local _host_umask
-
---- set umask to value used for build processes
-function e2tool.set_umask()
-    e2lib.umask(_chroot_umask)
-end
-
---- set umask back to the value used on the host
-function e2tool.reset_umask()
-    e2lib.umask(_host_umask)
-end
-
---- initialize the umask set/reset mechanism (i.e. store the host umask)
-local function init_umask()
-    -- save the umask value we run with
-    _host_umask = e2lib.umask(_chroot_umask)
-
-    -- restore the previous umask value again
-    e2tool.reset_umask()
-end
-
---- Set a new info table.
--- @param t Table to use for info.
--- @return The new info table.
-local function set_info(t)
-    assertIsTable(t)
-    _info = t
-    return _info
-end
-
---- Return the info table.
--- @return Info table on success,
---         false if the info table has not been initialised yet.
-function e2tool.info()
-    return _info
-end
-
-local _current_tool
---- Get current local tool name.
--- @param tool Optional new tool name.
--- @return Tool name
--- @raise Assert if tool not set/invalid.
-function e2tool.current_tool(tool)
-    if tool then
-        assertIsStringN(tool)
-        _current_tool = tool
-    end
-
-    assertIsStringN(_current_tool)
-    return _current_tool
-end
-
-local _project_root
---- Get (set) project root.
--- @param project_root Optional. Set to specify project root.
--- @return Project root directory.
-function e2tool.root(project_root)
-    if project_root then
-        assertIsStringN(project_root)
-        _project_root = project_root
-    end
-
-    assertIsStringN(_project_root)
-    return _project_root
-end
-
---- initialize the local library, load and initialize local plugins
--- @param path string: path to project tree (optional)
--- @param tool string: tool name (without the 'e2-' prefix)
--- @return table: the info table, or false on failure
--- @return an error object on failure
-function e2tool.local_init(path, tool)
-    local rc, re
-    local e = err.new("initializing local tool")
-    local info
-
-    info = set_info({})
-
-    e2tool.current_tool(tool)
-
-    rc, re = e2lib.cwd()
-    if not rc then
-        return false, e:cat(re)
-    end
-    info.startup_cwd = rc
-
-    init_umask(info)
-
-    rc, re = e2lib.locate_project_root(path)
-    if not rc then
-        return false, e:append("not located in a project directory")
-    end
-    e2tool.root(rc)
-
-    -- load local plugins
-    local ctx = {  -- plugin context
-        info = info,
-    }
-    local plugindir = e2lib.join(e2tool.root(), ".e2/plugins")
-    rc, re = plugin.load_plugins(plugindir, ctx)
-    if not rc then
-        return false, e:cat(re)
-    end
-    rc, re = plugin.init_plugins()
-    if not rc then
-        return false, e:cat(re)
-    end
-
-    return info
-end
-
 --- check for configuration syntax compatibility and log informational
 -- message including list of supported syntaxes if incompatibility is
 -- detected.
@@ -835,129 +685,106 @@ local function check_global_interface_version()
     return true
 end
 
---- Verify that a result or source file pathname in the form
--- "group1/group2/name" contains only valid characters.
--- Note that the path to the project root does not share the same constraints,
--- it's an error to pass it to this function.
---
--- @param pathname Relative path to a source or result, including
--- sub-directories (string).
--- @return True when the path is legal, false otherwise.
+--- Open debug logfile.
+-- @return True on success, false on error.
 -- @return Error object on failure.
-function e2tool.verify_src_res_pathname_valid_chars(pathname)
-    local msg = "only alphanumeric characters and '-_/' are allowed"
-    if not pathname:match("^[-_0-9a-zA-Z/]+$") then
-        return false, err.new(msg)
-    end
+local function opendebuglogfile()
+    local rc, re, e, logfile, debuglogfile
 
-    return true
-end
+    rc, re = e2lib.mkdir_recursive(e2lib.join(e2tool.root(), "log"))
+    if not rc then
+        e = err.new("error making log directory")
+        return false, e:cat(re)
+    end
+    logfile = e2lib.join(e2tool.root(), "log/debug.log")
+    rc, re = e2lib.rotate_log(logfile)
+    if not rc then
+        return false, re
+    end
 
---- Verify that a result or source name in the form "group1.group2.name"
--- contains only valid characters.
---
--- @param name Full source or result name, including groups (string).
--- @return True when the name is legal, false otherwise.
--- @return Error object on failure.
-function e2tool.verify_src_res_name_valid_chars(name)
-    local msg = "only alphanumeric characters and '-_.' are allowed"
-    if not name:match("^[-_0-9a-zA-Z.]+$") then
-        return false, err.new(msg)
+    debuglogfile, re = eio.fopen(logfile, "w")
+    if not debuglogfile then
+        e = err.new("error opening debug logfile")
+        return false, e:cat(re)
     end
 
+    e2lib.globals.debuglogfile = debuglogfile
+
     return true
 end
 
---- Convert source or result name, including groups, to a file system path.
--- @param name Name of a src or res, with optional group notation (string).
--- @return File system path equivalent of the input.
-function e2tool.src_res_name_to_path(name)
-    return name:gsub("%.", "/")
-end
+--- @type e2project_class
+e2tool.e2project_class = class("e2project_class")
 
---- Convert file system path of a source or result, including sub-directories
--- to group notation separated by dots.
--- @param pathname File system path of a src or res, with optional
--- sub-directories (string).
--- @return Group dot notation equivalent of the input.
-function e2tool.src_res_path_to_name(pathname)
-    return pathname:gsub("/", ".")
+function e2tool.e2project_class:initialize()
+    self._rootdir = false
+    self._server_project_location = false
+    self._info = false
+
+    -- set the umask value to be used in chroot
+    self._chroot_umask = 18 -- 022
+    self._host_umask = false
+
+    self._results = false
+    self._sources = false
 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).
--- @param name Optional result path component (string).
--- @param prefix Optional prefix path.
--- @return Path of the result.
-function e2tool.resultdir(name, prefix)
-    local p = "res"
-    if name then
-        p = e2lib.join(p, name)
+--- Initialize the local project, load and initialize local plugins.
+-- @param tool string: tool name (without the 'e2-' prefix)
+-- @raise Error on failure to initialize the project.
+function e2tool.e2project_class:init_project(tool)
+    assertIsStringN(tool)
+
+    local e = err.new("initializing local project")
+    local rc, re
+    local info
+
+    info = self:info({})
+
+    e2tool.current_tool(tool)
+
+    rc, re = e2lib.cwd()
+    if not rc then
+        error(e:cat(re))
     end
-    if prefix then
-        p = e2lib.join(prefix, p)
+    info.startup_cwd = rc
+
+    self:_init_umask()
+
+    rc, re = e2lib.locate_project_root()
+    if not rc then
+        error(e:cat("not located in a project directory"))
     end
-    return p
-end
+    e2tool.root(rc)
+    self:rootdir(rc)
 
---- Get project-relative directory for a source.
--- Returns the relative path to the sourcedir and optinally a name and prefix
--- (e.g. prefix/src/name).
--- @param name Optional source path component (string).
--- @param prefix Optional prefix path.
--- @return Path of the source.
-function e2tool.sourcedir(name, prefix)
-    local p = "src"
-    if name then
-        p = e2lib.join(p, name)
+    -- load local plugins
+    local ctx = {  -- plugin context
+        info = info,
+    }
+    local plugindir = e2lib.join(self:rootdir(), ".e2/plugins")
+    rc, re = plugin.load_plugins(plugindir, ctx)
+    if not rc then
+        error(e:cat(re))
     end
-    if prefix then
-        p = e2lib.join(prefix, p)
+    rc, re = plugin.init_plugins()
+    if not rc then
+        error(e:cat(re))
     end
-    return p
-end
 
---- Get project-relative path to the result config.
--- @param name Result path component.
--- @param prefix Optional prefix path.
--- @return Path to the resultconfig.
-function e2tool.resultconfig(name, prefix)
-    assert(type(name) == "string")
-    assert(prefix == nil or type(prefix) == "string")
-    return e2lib.join(e2tool.resultdir(name, prefix), "config")
-end
-
---- Get project-relative path to the result build-script
--- @param name Result path compnent name.
--- @param prefix Optional prefix path.
--- @return Path to the result build-script.
-function e2tool.resultbuildscript(name, prefix)
-    assert(type(name) == "string")
-    assert(prefix == nil or type(prefix) == "string")
-    return e2lib.join(e2tool.resultdir(name, prefix), "build-script")
+    strict.lock(info)
 end
 
---- Get project-relative path to the source config.
--- @param name Source path component.
--- @param prefix Optional prefix path.
--- @return Path to the sourceconfig.
-function e2tool.sourceconfig(name, prefix)
-    assert(type(name) == "string")
-    assert(prefix == nil or type(prefix) == "string")
-    return e2lib.join(e2tool.sourcedir(name, prefix), "config")
+function e2tool.e2project_class:_init_umask()
+    self._host_umask = e2lib.umask(self._chroot_umask)
+    e2tool.reset_umask()
 end
 
---- collect project info.
--- @param info Info table.
--- @param skip_load_config If true, skip loading config files etc.
--- @return True on success, false on error.
--- @return Error object on failure.
-function e2tool.collect_project_info(info, skip_load_config)
+--- Load the project configuration.
+function e2tool.e2project_class:load_project(skip_load_config)
+    local e = err.new("error loading project configuration")
     local rc, re
-    local e = err.new("reading project configuration")
 
-    -- check for configuration compatibility
     rc, re = check_config_syntax_compat()
     if not rc then
         e2lib.abort(re)
@@ -968,7 +795,10 @@ function e2tool.collect_project_info(info, skip_load_config)
         e2lib.abort(re)
     end
 
-    info.local_template_path = e2lib.join(e2tool.root(), ".e2/lib/e2/templates")
+    rc, re = opendebuglogfile()
+    if not rc then
+        return false, e:cat(re)
+    end
 
     rc, re = e2lib.init2() -- configuration must be available
     if not rc then
@@ -976,29 +806,25 @@ function e2tool.collect_project_info(info, skip_load_config)
     end
 
     if skip_load_config == true then
-        return info
+        return true
     end
 
-    rc, re = opendebuglogfile()
-    if not rc then
-        return false, e:cat(re)
-    end
 
     e2lib.logf(4, "VERSION:       %s", buildconfig.VERSION)
     e2lib.logf(4, "VERSIONSTRING: %s", buildconfig.VERSIONSTRING)
 
     -- read .e2/proj-location
-    local plf = e2lib.join(e2tool.root(), e2lib.globals.project_location_file)
+    local plf = e2lib.join(self:rootdir(), e2lib.globals.project_location_file)
     local line, re = eio.file_read_line(plf)
     if not line then
         return false, e:cat(re)
     end
     local _, _, l = string.find(line, "^%s*(%S+)%s*$")
     if not l then
-        return false, e:append("%s: can't parse project location", plf)
+        return false, e:cat("%s: can't parse project location", plf)
     end
-    info.project_location = l
-    e2lib.logf(4, "project location is %s", info.project_location)
+    self:project_location(l)
+    e2lib.logf(4, "project location is %s", self:project_location())
 
     -- setup cache
     local config, re = e2lib.get_global_config()
@@ -1013,7 +839,8 @@ function e2tool.collect_project_info(info, skip_load_config)
 
     cache.cache(rc)
 
-    rc, re = cache.setup_cache_local(cache.cache(), e2tool.root(), info.project_location)
+    rc, re = cache.setup_cache_local(
+        cache.cache(), self:rootdir(), self:project_location())
     if not rc then
         return false, e:cat(re)
     end
@@ -1023,14 +850,14 @@ function e2tool.collect_project_info(info, skip_load_config)
         return false, e:cat(re)
     end
 
-    local f = e2lib.join(e2tool.root(), e2lib.globals.e2version_file)
+    local f = e2lib.join(self:rootdir(), e2lib.globals.e2version_file)
     local v, re = e2lib.parse_e2versionfile(f)
     if not v then
-        return false, re
+        return false, e:cat(re)
     end
 
     if v.tag ~= buildconfig.VERSIONSTRING then
-        return false, err.new("local tool version does not match the " ..
+        return false, e:cat("local tool version does not match the " ..
             "version configured\n in `%s`\nlocal tool version is %s\n" ..
             "required version is %s", f, buildconfig.VERSIONSTRING, v.tag)
     end
@@ -1083,10 +910,6 @@ function e2tool.collect_project_info(info, skip_load_config)
         return false, e:cat(re)
     end
 
-    if e:getcount() > 1 then
-        return false, e
-    end
-
     -- warn if deprecated config files still exist
     local deprecated_files = {
         "proj/servers",
@@ -1099,7 +922,8 @@ function e2tool.collect_project_info(info, skip_load_config)
     for _,f in ipairs(deprecated_files) do
         local path = e2lib.join(e2tool.root(), f)
         if e2lib.exists(path) then
-            e2lib.warnf("WDEPRECATED", "File exists but is no longer used: `%s'", f)
+            e2lib.warnf("WDEPRECATED",
+                "File exists but is no longer used: `%s'", f)
         end
     end
 
@@ -1109,40 +933,296 @@ function e2tool.collect_project_info(info, skip_load_config)
     end
 
     if e2option.opts["check"] then
-        local dirty, mismatch
-
-        rc, re, mismatch = generic_git.verify_head_match_tag(e2tool.root(),
-            project.release_id())
+        rc, re = self:check_local()
         if not rc then
-            if mismatch then
-                e:append("project repository tag does not match " ..
-                    "the ReleaseId given in proj/config")
-            else
-                return false, e:cat(re)
-            end
+            return false, e:cat(re)
         end
+    end
 
-        rc, re, dirty = generic_git.verify_clean_repository(e2tool.root())
+    if e2option.opts["check-remote"] then
+        rc, re = self:check_remote()
         if not rc then
-            if dirty then
-                e = err.new("project repository is not clean")
-                return false, e:cat(re)
-            else
-                return false, e:cat(re)
-            end
+            return false, e:cat(re)
         end
     end
 
-    if e2option.opts["check-remote"] then
-        rc, re = generic_git.verify_remote_tag(
-            e2lib.join(e2tool.root(), ".git"), project.release_id())
-        if not rc then
-            e:append("verifying remote tag failed")
+    return true
+end
+
+--- Get or set the root directory of the e2 project.
+-- @param r rootdir to set, or nil
+-- @return rootdir string.
+-- @raise Assert on bad input
+function e2tool.e2project_class:rootdir(r)
+    if r then
+        assertIsStringN(r)
+        self._rootdir = r
+    else
+        assertIsStringN(self._rootdir)
+    end
+    return self._rootdir
+end
+
+--- Get the local template directory.
+-- @return local template directory.
+function e2tool.e2project_class:local_template_path()
+    return e2lib.join(self:rootdir(), ".e2/lib/e2/templates")
+end
+
+--- Get or set *server* project location
+-- @param l project location to set, or nil.
+-- @return project location.
+-- @raise Assert on bad input or unset project location.
+function e2tool.e2project_class:project_location(l)
+    if l then
+        assertIsStringN(l)
+        self._server_project_location = l
+    else
+        assertIsStringN(self._server_project_location)
+    end
+    return self._server_project_location
+end
+
+function e2tool.e2project_class:info(i)
+    if i then
+        assertIsTable(i)
+        self._info = i
+    else
+        assertIsTable(self._info)
+    end
+    return self._info
+end
+
+function e2tool.e2project_class:check_local()
+    local e, rc, re
+    local dirty, mismatch
+
+    e = err.new("checking project (local)")
+
+    rc, re, mismatch = generic_git.verify_head_match_tag(
+        self:rootdir(), project.release_id())
+    if not rc then
+        if mismatch then
+            e:append("project repository tag does not match " ..
+                "the ReleaseId given in proj/config")
+        else
             return false, e:cat(re)
         end
     end
 
-    return strict.lock(info)
+    rc, re, dirty = generic_git.verify_clean_repository(self:rootdir())
+    if not rc then
+        if dirty then
+            e:append("project repository is not clean")
+        else
+            return false, e:cat(re)
+        end
+    end
+
+    if e:getcount() > 1 then
+        return false, e
+    end
+    return true
+end
+
+function e2tool.e2project_class:check_remote()
+    local e, rc, re
+    e = err.new("checking project (remote)")
+
+    rc, re = generic_git.verify_remote_tag(
+        e2lib.join(self:rootdir(), ".git"), project.release_id())
+    if not rc then
+        e:append("verifying remote tag failed")
+        return false, e:cat(re)
+    end
+
+    return true
+end
+
+--- @section end
+
+--- Info table contains servers, caches and more...
+-- @table info
+-- @field startup_cwd Current working dir at startup (string).
+-- @field chroot_umask Umask setting for chroot (decimal number).
+-- @field host_umask Default umask of the process (decimal number).
+
+
+--- set umask to value used for build processes
+function e2tool.set_umask()
+    e2lib.umask(e2tool.e2project()._chroot_umask) -- XXX: remove hack
+end
+
+--- set umask back to the value used on the host
+function e2tool.reset_umask()
+    e2lib.umask(e2tool.e2project()._host_umask) -- XXX: remove hack
+end
+
+--- Set a new info table.
+-- @param t Table to use for info.
+-- @return The new info table.
+--[[local function set_info(t)
+    assertIsTable(t)
+    _info = t
+    return _info
+end]]
+
+--- Return the info table.
+-- @return Info table on success,
+--         false if the info table has not been initialised yet.
+function e2tool.info()
+    return e2tool.e2project():info()
+end
+
+local _current_tool
+--- Get current local tool name.
+-- @param tool Optional new tool name.
+-- @return Tool name
+-- @raise Assert if tool not set/invalid.
+function e2tool.current_tool(tool)
+    if tool then
+        assertIsStringN(tool)
+        _current_tool = tool
+    end
+
+    assertIsStringN(_current_tool)
+    return _current_tool
+end
+
+local _project_root
+--- Get (set) project root.
+-- @param project_root Optional. Set to specify project root.
+-- @return Project root directory.
+function e2tool.root(project_root)
+    if project_root then
+        assertIsStringN(project_root)
+        _project_root = project_root
+    end
+
+    assertIsStringN(_project_root)
+    return _project_root
+end
+
+local _the_e2project
+--- Get the e2project singleton.
+function e2tool.e2project()
+    if not _the_e2project then
+        _the_e2project = e2tool.e2project_class:new()
+    end
+    return _the_e2project
+end
+
+--- Verify that a result or source file pathname in the form
+-- "group1/group2/name" contains only valid characters.
+-- Note that the path to the project root does not share the same constraints,
+-- it's an error to pass it to this function.
+--
+-- @param pathname Relative path to a source or result, including
+-- sub-directories (string).
+-- @return True when the path is legal, false otherwise.
+-- @return Error object on failure.
+function e2tool.verify_src_res_pathname_valid_chars(pathname)
+    local msg = "only alphanumeric characters and '-_/' are allowed"
+    if not pathname:match("^[-_0-9a-zA-Z/]+$") then
+        return false, err.new(msg)
+    end
+
+    return true
+end
+
+--- Verify that a result or source name in the form "group1.group2.name"
+-- contains only valid characters.
+--
+-- @param name Full source or result name, including groups (string).
+-- @return True when the name is legal, false otherwise.
+-- @return Error object on failure.
+function e2tool.verify_src_res_name_valid_chars(name)
+    local msg = "only alphanumeric characters and '-_.' are allowed"
+    if not name:match("^[-_0-9a-zA-Z.]+$") then
+        return false, err.new(msg)
+    end
+
+    return true
+end
+
+--- Convert source or result name, including groups, to a file system path.
+-- @param name Name of a src or res, with optional group notation (string).
+-- @return File system path equivalent of the input.
+function e2tool.src_res_name_to_path(name)
+    return name:gsub("%.", "/")
+end
+
+--- Convert file system path of a source or result, including sub-directories
+-- to group notation separated by dots.
+-- @param pathname File system path of a src or res, with optional
+-- sub-directories (string).
+-- @return Group dot notation equivalent of the input.
+function e2tool.src_res_path_to_name(pathname)
+    return pathname:gsub("/", ".")
+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).
+-- @param name Optional result path component (string).
+-- @param prefix Optional prefix path.
+-- @return Path of the result.
+function e2tool.resultdir(name, prefix)
+    local p = "res"
+    if name then
+        p = e2lib.join(p, name)
+    end
+    if prefix then
+        p = e2lib.join(prefix, p)
+    end
+    return p
+end
+
+--- Get project-relative directory for a source.
+-- Returns the relative path to the sourcedir and optinally a name and prefix
+-- (e.g. prefix/src/name).
+-- @param name Optional source path component (string).
+-- @param prefix Optional prefix path.
+-- @return Path of the source.
+function e2tool.sourcedir(name, prefix)
+    local p = "src"
+    if name then
+        p = e2lib.join(p, name)
+    end
+    if prefix then
+        p = e2lib.join(prefix, p)
+    end
+    return p
+end
+
+--- Get project-relative path to the result config.
+-- @param name Result path component.
+-- @param prefix Optional prefix path.
+-- @return Path to the resultconfig.
+function e2tool.resultconfig(name, prefix)
+    assert(type(name) == "string")
+    assert(prefix == nil or type(prefix) == "string")
+    return e2lib.join(e2tool.resultdir(name, prefix), "config")
+end
+
+--- Get project-relative path to the result build-script
+-- @param name Result path compnent name.
+-- @param prefix Optional prefix path.
+-- @return Path to the result build-script.
+function e2tool.resultbuildscript(name, prefix)
+    assert(type(name) == "string")
+    assert(prefix == nil or type(prefix) == "string")
+    return e2lib.join(e2tool.resultdir(name, prefix), "build-script")
+end
+
+--- Get project-relative path to the source config.
+-- @param name Source path component.
+-- @param prefix Optional prefix path.
+-- @return Path to the sourceconfig.
+function e2tool.sourceconfig(name, prefix)
+    assert(type(name) == "string")
+    assert(prefix == nil or type(prefix) == "string")
+    return e2lib.join(e2tool.sourcedir(name, prefix), "config")
 end
 
 --- Returns a sorted vector with all depdencies of result, and all