mirror of
ssh://git.janware.com/janware/proj/jw-pkg
synced 2026-04-25 09:35:54 +02:00
jw.pkg.App: Code beautification
Major - but not yet sufficient - code beautification starting from
jw.pkg.App.
- Make more methods private
- Rename methods to be more self-explanatory
- Same for method arguments, notably clean up some inconsistent
uses of "module" vs "project"
- Add more type hints
Fix API breakage in the command modules.
Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
parent
2bbf5bd8e7
commit
6ca4af77d7
12 changed files with 204 additions and 209 deletions
|
|
@ -3,6 +3,8 @@
|
|||
# This source code file is a merge of various build tools and a horrible mess.
|
||||
#
|
||||
|
||||
from typing import TypeAlias
|
||||
|
||||
import os, sys, argparse, pwd, re
|
||||
from functools import lru_cache
|
||||
from enum import Enum, auto
|
||||
|
|
@ -10,12 +12,12 @@ from enum import Enum, auto
|
|||
from .lib.App import App as Base
|
||||
from .lib.log import *
|
||||
|
||||
# meaning of pkg.requires.xxx variables
|
||||
# Meaning of pkg.requires.xxx variables
|
||||
# build: needs to be built and installed before this can be built
|
||||
# devel: needs to be installed before this-devel can be installed, i.e. before _other_ packages can be built against this
|
||||
# run: needs to be installed before this-run can be installed, i.e. before this and other packages can run with this
|
||||
|
||||
# --------------------------------------------------------------------- helpers
|
||||
# --------------------------------------------------------------------- Helpers
|
||||
|
||||
class ResultCache(object):
|
||||
|
||||
|
|
@ -52,6 +54,8 @@ class Scope(Enum):
|
|||
One = auto()
|
||||
Subtree = auto()
|
||||
|
||||
Graph: TypeAlias = dict[str, set[str]]
|
||||
|
||||
# ----------------------------------------------------------------- class App
|
||||
|
||||
class App(Base):
|
||||
|
|
@ -85,7 +89,7 @@ class App(Base):
|
|||
return None
|
||||
raise Exception('No project path found for module "{}"'.format(name))
|
||||
|
||||
def __find_dir(self, name: str, search_subdirs: list[str]=[], search_absdirs: list[str]=[], pretty: bool=True):
|
||||
def __find_dir(self, name: str, search_subdirs: list[str]=[], search_absdirs: list[str]=[], pretty: bool=True) -> str|None:
|
||||
def format_pd(name: str, pd: str, pretty: bool):
|
||||
if not pretty:
|
||||
return pd
|
||||
|
|
@ -114,226 +118,11 @@ class App(Base):
|
|||
return ret
|
||||
return None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("jw-pkg swiss army knife", modules=["jw.pkg.cmds"])
|
||||
# -- Members without default values
|
||||
self.__opt_os: str|None = None
|
||||
self.__top_name: str|None = None
|
||||
self.__os_cascade: list[str]|None = None
|
||||
self.__res_cache = ResultCache()
|
||||
self.__topdir: str|None = None
|
||||
self.__pretty_topdir: str|None = None
|
||||
def __get_project_refs_cached(self, buf, visited, spec, section, key, add_self, scope, names_only):
|
||||
return self.__res_cache.run(self.__get_project_refs, [buf, visited, spec, section, key, add_self, scope, names_only])
|
||||
|
||||
# -- Members with default values
|
||||
self.__topdir_fmt = 'absolute'
|
||||
self.__projs_root = pwd.getpwuid(os.getuid()).pw_dir + "/local/src/jw.dev/proj"
|
||||
self.__pretty_projs_root = None
|
||||
|
||||
def _add_arguments(self, parser):
|
||||
super()._add_arguments(parser)
|
||||
parser.add_argument('-t', '--topdir', default = None, help='Project Path')
|
||||
parser.add_argument('--topdir-format', default = 'absolute', help='Output references to topdir as '
|
||||
+ 'one of "make:<var-name>", "unaltered", "absolute". Absolute topdir by default')
|
||||
parser.add_argument('-p', '--prefix', default = None,
|
||||
help='Parent directory of project source directories')
|
||||
parser.add_argument('-O', '--os', default = None, help='Target operating system')
|
||||
|
||||
async def _run(self, args: argparse.Namespace) -> None:
|
||||
|
||||
self.__opt_os = args.os
|
||||
self.__topdir = args.topdir
|
||||
self.__pretty_topdir = self.__format_topdir(self.__topdir, args.topdir_format)
|
||||
self.__topdir_fmt = args.topdir_format
|
||||
if self.__topdir is not None:
|
||||
self.__top_name = self.read_value(self.__topdir + '/make/project.conf', 'build', 'name')
|
||||
if not self.__top_name:
|
||||
self.__top_name = re.sub('-[0-9.-]*$', '', os.path.basename(os.path.realpath(self.__topdir)))
|
||||
|
||||
if args.prefix is not None:
|
||||
self.__projs_root = args.prefix
|
||||
self.__pretty_projs_root = args.prefix
|
||||
|
||||
return await super()._run(args)
|
||||
|
||||
@property
|
||||
def top_name(self):
|
||||
return self.__top_name
|
||||
|
||||
def find_dir(self, name: str, search_subdirs: list[str]=[], search_absdirs: list[str]=[], pretty: bool=True):
|
||||
return self.__find_dir(name, search_subdirs, search_absdirs, pretty)
|
||||
|
||||
def re_section(self, name):
|
||||
return re.compile('[' + name + ']'
|
||||
'.*?'
|
||||
'(?=[)',
|
||||
re.DOTALL)
|
||||
|
||||
def remove_duplicates(self, seq):
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
return [x for x in seq if not (x in seen or seen_add(x))]
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_os(self, args = ""):
|
||||
import subprocess
|
||||
for d in [ self.__projs_root + '/jw-pkg/scripts', '/opt/jw-pkg/bin' ]:
|
||||
script = d + '/get-os.sh'
|
||||
if os.path.isfile(script):
|
||||
cmd = '/bin/bash ' + script
|
||||
if args:
|
||||
cmd = cmd + ' ' + args
|
||||
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
|
||||
(out, rr) = p.communicate()
|
||||
if rr:
|
||||
log(ERR, "failed to run ", cmd)
|
||||
continue
|
||||
out = re.sub('\n', '', out.decode('utf-8'))
|
||||
return out
|
||||
return "linux"
|
||||
|
||||
# TODO: add support for customizing this in project.conf
|
||||
def htdocs_dir(self, name: str) -> str:
|
||||
return self.find_dir(name,["/src/html/htdocs", "/tools/html/htdocs", "/htdocs"],
|
||||
["/srv/www/proj/" + name])
|
||||
|
||||
# TODO: add support for customizing this in project.conf
|
||||
def tmpl_dir(self, name: str) -> str:
|
||||
return self.find_dir(name, ["/tmpl"], ["/opt/" + name + "/share/tmpl"])
|
||||
|
||||
def os_cascade(self):
|
||||
import platform
|
||||
if self.__os_cascade is not None:
|
||||
return self.__os_cascade.copy()
|
||||
r = [ 'os', platform.system().lower() ]
|
||||
os = self.__opt_os if self.__opt_os is not None else self.get_os()
|
||||
name = re.sub('-.*', '', os)
|
||||
series = os
|
||||
while True:
|
||||
n = re.sub(r'\.[0-9]+$', '', series)
|
||||
if n == series:
|
||||
break
|
||||
r.append(n)
|
||||
series = n
|
||||
if not name in r:
|
||||
r.append(name)
|
||||
if not os in r:
|
||||
r.append(os)
|
||||
# e.g. os, linux, suse, suse-tumbleweed
|
||||
#return [ 'os', platform.system().lower(), name, os ]
|
||||
self.__os_cascade = r
|
||||
return r
|
||||
|
||||
def strip_module_from_spec(self, mod):
|
||||
return re.sub(r'-dev$|-devel$|-run$', '', re.split('([=><]+)', mod)[0].strip())
|
||||
|
||||
def get_section(self, path, section):
|
||||
r = ''
|
||||
file = open(path)
|
||||
pat = '[' + section + ']'
|
||||
in_section = False
|
||||
for line in file:
|
||||
if (line.rstrip() == pat):
|
||||
in_section = True
|
||||
continue
|
||||
if in_section:
|
||||
if len(line) and line[0] == '[':
|
||||
break
|
||||
r = r + line
|
||||
file.close()
|
||||
return r.rstrip()
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def read_value(self, path, section, key):
|
||||
|
||||
def scan_section(f, key):
|
||||
if key is None:
|
||||
r = ''
|
||||
for line in f:
|
||||
if len(line) and line[0] == '[':
|
||||
break
|
||||
r += line
|
||||
return r if len(r) else None
|
||||
lines = []
|
||||
cont_line = ''
|
||||
for line in f:
|
||||
if len(line) and line[0] == '[':
|
||||
break
|
||||
cont_line += line.rstrip()
|
||||
if len(cont_line) and cont_line[-1] == '\\':
|
||||
cont_line = cont_line[0:-1]
|
||||
continue
|
||||
lines.append(cont_line)
|
||||
cont_line = ''
|
||||
for line in lines:
|
||||
#log(DEBUG, " looking for >%s< in line=>%s<" % (key, line))
|
||||
rr = re.findall('^ *' + key + ' *= *(.*)', line)
|
||||
if len(rr) > 0:
|
||||
return rr[0]
|
||||
return None
|
||||
|
||||
def scan_section_debug(f, key):
|
||||
rr = scan_section(f, key)
|
||||
#log(DEBUG, " returning", rr)
|
||||
return rr
|
||||
|
||||
try:
|
||||
#log(DEBUG, "looking for {}::[{}].{}".format(path, section, key))
|
||||
with open(path, 'r') as f:
|
||||
if not len(section):
|
||||
rr = scan_section(f, key)
|
||||
pat = '[' + section + ']'
|
||||
for line in f:
|
||||
if line.rstrip() == pat:
|
||||
return scan_section(f, key)
|
||||
return None
|
||||
except:
|
||||
log(DEBUG, path, "not found")
|
||||
# TODO: handle this special case cleaner somewhere up the stack
|
||||
if section == 'build' and key == 'libname':
|
||||
return 'none'
|
||||
return None
|
||||
|
||||
def get_value(self, name, section, key):
|
||||
log(DEBUG, "getting value [%s].%s for project %s (%s)" %(section, key, name, self.__top_name))
|
||||
if self.__top_name and name == self.__top_name:
|
||||
proj_root = self.__topdir
|
||||
else:
|
||||
proj_root = self.__projs_root + '/' + name
|
||||
log(DEBUG, "proj_root = " + proj_root)
|
||||
|
||||
if section == 'version':
|
||||
proj_version_dirs = [ proj_root ]
|
||||
if proj_root != self.__topdir:
|
||||
proj_version_dirs.append('/usr/share/doc/packages/' + name)
|
||||
for d in proj_version_dirs:
|
||||
version_path = d + '/VERSION'
|
||||
try:
|
||||
with open(version_path) as fd:
|
||||
r = fd.read().replace('\n', '').replace('-dev', '')
|
||||
fd.close()
|
||||
return r
|
||||
except EnvironmentError:
|
||||
log(DEBUG, "ignoring unreadable file " + version_path)
|
||||
continue
|
||||
raise Exception("No version file found for project \"" + name + "\"")
|
||||
|
||||
path = proj_root + '/make/project.conf'
|
||||
#print('path = ', path, 'self.__top_name = ', self.__top_name, 'name = ', name)
|
||||
return self.read_value(path, section, key)
|
||||
|
||||
def collect_values(self, names, section, key):
|
||||
r = ""
|
||||
for n in names:
|
||||
val = self.get_value(n, section, key)
|
||||
if val:
|
||||
r = r + " " + val
|
||||
return self.remove_duplicates([x.strip() for x in r.split(",")])
|
||||
|
||||
def add_modules_from_project_txt_cached(self, buf, visited, spec, section, key, add_self, scope, names_only):
|
||||
return self.__res_cache.run(self.add_modules_from_project_txt, [buf, visited, spec, section, key, add_self, scope, names_only])
|
||||
|
||||
def add_modules_from_project_txt(self, buf: list[str], visited: set[str], spec: str,
|
||||
section: str, key: str, add_self: bool, scope: Scope, names_only: bool):
|
||||
def __get_project_refs(self, buf: list[str], visited: set[str], spec: str,
|
||||
section: str, key: str, add_self: bool, scope: Scope, names_only: bool) -> None:
|
||||
name = self.strip_module_from_spec(spec)
|
||||
if names_only:
|
||||
spec = name
|
||||
|
|
@ -356,43 +145,304 @@ class App(Base):
|
|||
dep = dep.strip()
|
||||
if not(len(dep)):
|
||||
continue
|
||||
self.add_modules_from_project_txt_cached(buf, visited, dep,
|
||||
self.__get_project_refs_cached(buf, visited, dep,
|
||||
section, key, add_self=True, scope=subscope,
|
||||
names_only=names_only)
|
||||
if add_self:
|
||||
buf.append(spec)
|
||||
|
||||
def get_modules_from_project_txt(self, names, sections, keys, add_self, scope,
|
||||
names_only = True):
|
||||
def __read_dep_graph(self, projects: list[str], section: str, graph: Graph) -> None:
|
||||
for project in projects:
|
||||
if project in graph:
|
||||
continue
|
||||
deps = self.get_project_refs([ project ], ['pkg.requires.jw'], section,
|
||||
scope = Scope.One, add_self=False, names_only=True)
|
||||
if not deps is None:
|
||||
graph[project] = set(deps)
|
||||
for dep in deps:
|
||||
self.__read_dep_graph([ dep ], section, graph)
|
||||
|
||||
def __flip_dep_graph(self, graph: Graph):
|
||||
ret: Graph = {}
|
||||
for project, deps in graph.items():
|
||||
for d in deps:
|
||||
if not d in ret:
|
||||
ret[d] = set()
|
||||
ret[d].add(project)
|
||||
return ret
|
||||
|
||||
def __find_circular_deps_recursive(self, project: str, graph: Graph, unvisited: list[str],
|
||||
temp: set[str], path: str) -> str|None:
|
||||
if project in temp:
|
||||
log(DEBUG, 'found circular dependency at project', project)
|
||||
return project
|
||||
if not project in unvisited:
|
||||
return None
|
||||
temp.add(project)
|
||||
if project in graph:
|
||||
for dep in graph[project]:
|
||||
last = self.__find_circular_deps_recursive(dep, graph, unvisited, temp, path)
|
||||
if last is not None:
|
||||
path.insert(0, dep)
|
||||
return last
|
||||
unvisited.remove(project)
|
||||
temp.remove(project)
|
||||
|
||||
def __find_circular_deps(self, projects: list[str], flavours: list[str]) -> bool:
|
||||
graph: Graph = {}
|
||||
ret: list[str] = []
|
||||
self.__read_dep_graph(projects, flavours, graph)
|
||||
unvisited = list(graph.keys())
|
||||
temp: set[str] = set()
|
||||
while unvisited:
|
||||
project = unvisited[0]
|
||||
log(DEBUG, 'Checking circular dependency of', project)
|
||||
last = self.__find_circular_deps_recursive(project, self.__flip_dep_graph(graph), unvisited, temp, ret)
|
||||
if last is not None:
|
||||
log(DEBUG, f'Found circular dependency below {project}, last is {last}')
|
||||
return True
|
||||
return False
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
super().__init__("jw-pkg swiss army knife", modules=["jw.pkg.cmds"])
|
||||
|
||||
# -- Members without default values
|
||||
self.__opt_os: str|None = None
|
||||
self.__top_name: str|None = None
|
||||
self.__os_cascade: list[str]|None = None
|
||||
self.__res_cache = ResultCache()
|
||||
self.__topdir: str|None = None
|
||||
self.__pretty_topdir: str|None = None
|
||||
|
||||
# -- Members with default values
|
||||
self.__topdir_fmt = 'absolute'
|
||||
self.__projs_root = pwd.getpwuid(os.getuid()).pw_dir + "/local/src/jw.dev/proj"
|
||||
self.__pretty_projs_root = None
|
||||
|
||||
def _add_arguments(self, parser) -> None:
|
||||
super()._add_arguments(parser)
|
||||
parser.add_argument('-t', '--topdir', default = None, help='Project Path')
|
||||
parser.add_argument('--topdir-format', default = 'absolute', help='Output references to topdir as '
|
||||
+ 'one of "make:<var-name>", "unaltered", "absolute". Absolute topdir by default')
|
||||
parser.add_argument('-p', '--prefix', default = None,
|
||||
help='Parent directory of project source directories')
|
||||
parser.add_argument('-O', '--os', default = None, help='Target operating system')
|
||||
|
||||
async def _run(self, args: argparse.Namespace) -> None:
|
||||
self.__opt_os = args.os
|
||||
self.__topdir = args.topdir
|
||||
self.__pretty_topdir = self.__format_topdir(self.__topdir, args.topdir_format)
|
||||
self.__topdir_fmt = args.topdir_format
|
||||
if self.__topdir is not None:
|
||||
self.__top_name = self.read_value(self.__topdir + '/make/project.conf', 'build', 'name')
|
||||
if not self.__top_name:
|
||||
self.__top_name = re.sub('-[0-9.-]*$', '', os.path.basename(os.path.realpath(self.__topdir)))
|
||||
if args.prefix is not None:
|
||||
self.__projs_root = args.prefix
|
||||
self.__pretty_projs_root = args.prefix
|
||||
return await super()._run(args)
|
||||
|
||||
@property
|
||||
def top_name(self):
|
||||
return self.__top_name
|
||||
|
||||
def find_dir(self, name: str, search_subdirs: list[str]=[], search_absdirs: list[str]=[], pretty: bool=True):
|
||||
return self.__find_dir(name, search_subdirs, search_absdirs, pretty)
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_os(self, args="") -> str:
|
||||
import subprocess
|
||||
for d in [ self.__projs_root + '/jw-pkg/scripts', '/opt/jw-pkg/bin' ]:
|
||||
script = d + '/get-os.sh'
|
||||
if os.path.isfile(script):
|
||||
cmd = '/bin/bash ' + script
|
||||
if args:
|
||||
cmd = cmd + ' ' + args
|
||||
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
|
||||
(out, rr) = p.communicate()
|
||||
if rr:
|
||||
log(ERR, "failed to run ", cmd)
|
||||
continue
|
||||
out = re.sub('\n', '', out.decode('utf-8'))
|
||||
return out
|
||||
return "linux"
|
||||
|
||||
# TODO: add support for customizing this in project.conf
|
||||
def htdocs_dir(self, project: str) -> str:
|
||||
return self.find_dir(project, ["/src/html/htdocs", "/tools/html/htdocs", "/htdocs"],
|
||||
["/srv/www/proj/" + project])
|
||||
|
||||
# TODO: add support for customizing this in project.conf
|
||||
def tmpl_dir(self, name: str) -> str:
|
||||
return self.find_dir(name, ["/tmpl"], ["/opt/" + name + "/share/tmpl"])
|
||||
|
||||
def os_cascade(self) -> list[str]:
|
||||
import platform
|
||||
if self.__os_cascade is None:
|
||||
ret = [ 'os', platform.system().lower() ]
|
||||
os = self.__opt_os if self.__opt_os is not None else self.get_os()
|
||||
name = re.sub(r'-.*', '', os)
|
||||
series = os
|
||||
rx = re.compile(r'\.[0-9]+$')
|
||||
while True:
|
||||
n = re.sub(rx, '', series)
|
||||
if n == series:
|
||||
break
|
||||
ret.append(n)
|
||||
series = n
|
||||
if not name in ret:
|
||||
ret.append(name)
|
||||
if not os in ret:
|
||||
ret.append(os)
|
||||
# e.g. os, linux, suse, suse-tumbleweed
|
||||
#return [ 'os', platform.system().lower(), name, os ]
|
||||
self.__os_cascade = ret
|
||||
return self.__os_cascade
|
||||
|
||||
def strip_module_from_spec(self, mod):
|
||||
return re.sub(r'-dev$|-devel$|-run$', '', re.split('([=><]+)', mod)[0].strip())
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_section(self, path: str, section: str) -> str:
|
||||
ret = ''
|
||||
pat = '[' + section + ']'
|
||||
in_section = False
|
||||
file = open(path)
|
||||
for line in file:
|
||||
if (line.rstrip() == pat):
|
||||
in_section = True
|
||||
continue
|
||||
if in_section:
|
||||
if len(line) and line[0] == '[':
|
||||
break
|
||||
ret += line
|
||||
file.close()
|
||||
return ret.rstrip()
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def read_value(self, path: str, section: str, key: str) -> str|None:
|
||||
|
||||
def scan_section(f, key: str) -> str|None:
|
||||
if key is None:
|
||||
ret = ''
|
||||
for line in f:
|
||||
if len(line) and line[0] == '[':
|
||||
break
|
||||
ret += line
|
||||
return ret if len(ret) else None
|
||||
lines: list[str] = []
|
||||
cont_line = ''
|
||||
for line in f:
|
||||
if len(line) and line[0] == '[':
|
||||
break
|
||||
cont_line += line.rstrip()
|
||||
if len(cont_line) and cont_line[-1] == '\\':
|
||||
cont_line = cont_line[0:-1]
|
||||
continue
|
||||
lines.append(cont_line)
|
||||
cont_line = ''
|
||||
rx = re.compile(r'^\s*' + key + r'\s*=\s*(.*)\s*$')
|
||||
for line in lines:
|
||||
#log(DEBUG, " looking for >%s< in line=>%s<" % (key, line))
|
||||
m = re.search(rx, line)
|
||||
if m is not None:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
def scan_section_debug(f, key: str) -> str|None:
|
||||
ret = scan_section(f, key)
|
||||
#log(DEBUG, " returning", rr)
|
||||
return ret
|
||||
|
||||
try:
|
||||
#log(DEBUG, "looking for {}::[{}].{}".format(path, section, key))
|
||||
with open(path, 'r') as f:
|
||||
if not len(section):
|
||||
rr = scan_section(f, key)
|
||||
pat = '[' + section + ']'
|
||||
for line in f:
|
||||
if line.rstrip() == pat:
|
||||
return scan_section(f, key)
|
||||
return None
|
||||
except:
|
||||
log(DEBUG, path, "not found")
|
||||
# TODO: handle this special case cleaner somewhere up the stack
|
||||
if section == 'build' and key == 'libname':
|
||||
return 'none'
|
||||
return None
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_value(self, project: str, section: str, key: str) -> str:
|
||||
log(DEBUG, "getting value [%s].%s for project %s (%s)" %(section, key, project, self.__top_name))
|
||||
assert len(section) != 1
|
||||
if self.__top_name and project == self.__top_name:
|
||||
proj_root = self.__topdir
|
||||
else:
|
||||
proj_root = self.__projs_root + '/' + project
|
||||
if section == 'version':
|
||||
proj_version_dirs = [ proj_root ]
|
||||
if proj_root != self.__topdir:
|
||||
proj_version_dirs.append('/usr/share/doc/packages/' + project)
|
||||
for d in proj_version_dirs:
|
||||
version_path = d + '/VERSION'
|
||||
try:
|
||||
with open(version_path) as fd:
|
||||
ret = fd.read().replace('\n', '').replace('-dev', '')
|
||||
fd.close()
|
||||
return ret
|
||||
except EnvironmentError:
|
||||
log(DEBUG, f'"Ignoring unreadable file "{version_path}"')
|
||||
continue
|
||||
raise Exception(f'No version file found for project "{project}"')
|
||||
|
||||
path = proj_root + '/make/project.conf'
|
||||
#print('path = ', path, 'self.__top_name = ', self.__top_name, 'name = ', name)
|
||||
return self.read_value(path, section, key)
|
||||
|
||||
def get_values(self, projects: list[str], sections: list[str], keys: list[str]) -> list[str]:
|
||||
"""
|
||||
Collect a list of values from a list of given projects, sections and
|
||||
keys, maintaining order
|
||||
"""
|
||||
ret: list[str] = []
|
||||
for p in projects:
|
||||
for section in sections:
|
||||
for key in keys:
|
||||
vals = self.get_value(p, section, key)
|
||||
if vals:
|
||||
ret += [val.strip() for val in vals.split(",")]
|
||||
return list(dict.fromkeys(ret)) # Remove duplicates, keep ordering
|
||||
|
||||
def get_project_refs(self, projects: list[str], sections: list[str],
|
||||
keys: str|list[str], add_self: bool, scope: Scope, names_only=True) -> list[str]:
|
||||
if isinstance(keys, str):
|
||||
keys = [ keys ]
|
||||
#r = set()
|
||||
r = []
|
||||
ret: list[str] = []
|
||||
for section in sections:
|
||||
for key in keys:
|
||||
visited = set()
|
||||
for name in names:
|
||||
rr = []
|
||||
self.add_modules_from_project_txt_cached(rr, visited, name, section, key, add_self, scope,
|
||||
names_only)
|
||||
for name in projects:
|
||||
rr: list[str] = []
|
||||
self.__get_project_refs_cached(rr, visited, name, section, key, add_self, scope, names_only)
|
||||
# TODO: this looks like a performance hogger
|
||||
for m in rr:
|
||||
if not m in r:
|
||||
r.append(m)
|
||||
return r
|
||||
if not m in ret:
|
||||
ret.append(m)
|
||||
return ret
|
||||
|
||||
def get_libname(self, names):
|
||||
vals = self.get_modules_from_project_txt(names, ['build'], 'libname',
|
||||
scope = Scope.One, add_self=False, names_only=True)
|
||||
def get_libname(self, projects) -> str:
|
||||
vals = self.get_project_refs(projects, ['build'], 'libname',
|
||||
scope = Scope.One, add_self=False, projects_only=True)
|
||||
if not vals:
|
||||
return ' '.join(names)
|
||||
return ' '.join(projects)
|
||||
if 'none' in vals:
|
||||
vals.remove('none')
|
||||
return ' '.join(reversed(vals))
|
||||
|
||||
def is_excluded_from_build(self, module):
|
||||
log(DEBUG, "checking if module " + module + " is excluded from build")
|
||||
exclude = self.get_modules_from_project_txt([ module ], ['build'], 'exclude',
|
||||
def is_excluded_from_build(self, project: str) -> str|None:
|
||||
log(DEBUG, "checking if project " + project + " is excluded from build")
|
||||
exclude = self.get_project_refs([ project ], ['build'], 'exclude',
|
||||
scope = Scope.One, add_self=False, names_only=True)
|
||||
cascade = self.os_cascade() + [ 'all' ]
|
||||
for p1 in exclude:
|
||||
|
|
@ -401,47 +451,5 @@ class App(Base):
|
|||
return p1
|
||||
return None
|
||||
|
||||
def contains(self, small, big):
|
||||
for i in xrange(len(big)-len(small)+1):
|
||||
for j in xrange(len(small)):
|
||||
if big[i+j] != small[j]:
|
||||
break
|
||||
else:
|
||||
return i, i+len(small)
|
||||
return False
|
||||
|
||||
def read_dep_graph(self, modules, section, graph):
|
||||
for m in modules:
|
||||
if m in graph:
|
||||
continue
|
||||
deps = self.get_modules_from_project_txt([ m ], ['pkg.requires.jw'], section,
|
||||
scope = Scope.One, add_self=False, names_only=True)
|
||||
if not deps is None:
|
||||
graph[m] = deps
|
||||
for d in deps:
|
||||
self.read_dep_graph([ d ], section, graph)
|
||||
|
||||
def flip_graph(self, graph):
|
||||
r = {}
|
||||
for m, deps in graph.items():
|
||||
for d in deps:
|
||||
if not d in r:
|
||||
r[d] = set()
|
||||
r[d].add(m)
|
||||
return r
|
||||
|
||||
def check_circular_deps(self, module, section, graph, unvisited, temp, path):
|
||||
if module in temp:
|
||||
log(DEBUG, 'found circular dependency at module', module)
|
||||
return module
|
||||
if not module in unvisited:
|
||||
return None
|
||||
temp.add(module)
|
||||
if module in graph:
|
||||
for m in graph[module]:
|
||||
last = self.check_circular_deps(m, section, graph, unvisited, temp, path)
|
||||
if last is not None:
|
||||
path.insert(0, m)
|
||||
return last
|
||||
unvisited.remove(module)
|
||||
temp.remove(module)
|
||||
def find_circular_deps(self, projects: list[str], flavours: list[str]) -> bool:
|
||||
return self.__find_circular_deps(projects, flavours)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class CmdBuild(Cmd): # export
|
|||
else:
|
||||
dep_cache[prereq_type]: dict[str, str] = {}
|
||||
|
||||
ret = self.app.get_modules_from_project_txt([ cur ], ['pkg.requires.jw'],
|
||||
ret = self.app.get_project_refs([ cur ], ['pkg.requires.jw'],
|
||||
prereq_type, scope = Scope.Subtree, add_self=False, names_only=True)
|
||||
log(DEBUG, 'prerequisites = ' + ' '.join(ret))
|
||||
if cur in ret:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class CmdCflags(Cmd): # export
|
|||
parser.add_argument('module', nargs='*', help='Modules')
|
||||
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
deps = self.app.get_modules_from_project_txt(args.module, ['pkg.requires.jw'], 'build',
|
||||
deps = self.app.get_project_refs(args.module, ['pkg.requires.jw'], 'build',
|
||||
scope = Scope.Subtree, add_self=True, names_only=True)
|
||||
out = []
|
||||
for m in reversed(deps):
|
||||
|
|
|
|||
|
|
@ -17,19 +17,8 @@ class CmdCheck(Cmd): # export
|
|||
parser.add_argument('-f', '--flavour', nargs='?', default = 'build')
|
||||
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
graph = {}
|
||||
path = []
|
||||
self.app.read_dep_graph(args.module, args.flavour, graph)
|
||||
unvisited = list(graph.keys())
|
||||
temp = set()
|
||||
while len(unvisited) != 0:
|
||||
m = unvisited[0]
|
||||
log(DEBUG, 'Checking circular dependency of', m)
|
||||
last = self.app.check_circular_deps(m, args.flavour, self.app.flip_graph(graph), unvisited, temp, path)
|
||||
if last is not None:
|
||||
log(DEBUG, 'Found circular dependency below', m, ', last is', last)
|
||||
print('Found circular dependency in flavour', args.flavour, ':', ' -> '.join(path))
|
||||
exit(1)
|
||||
print('No circular dependency found for flavour', args.flavour, ' in modules:',
|
||||
' '.join(args.module))
|
||||
exit(0)
|
||||
path = self.app.find_circular_deps(args.module, args.flavour)
|
||||
if path:
|
||||
print(f'Found circular dependency in flavour {args.flavour}:', ' -> '.join(path))
|
||||
exit(1)
|
||||
print(f'No circular dependency found for flavour {args.flavour} in modules:', ' '.join(args.module))
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class CmdExepath(Cmd): # export
|
|||
parser.add_argument('module', nargs='*', help='Modules')
|
||||
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
deps = self.app.get_modules_from_project_txt(args.module, ['pkg.requires.jw'], [ 'run', 'build', 'devel' ],
|
||||
deps = self.app.get_project_refs(args.module, ['pkg.requires.jw'], [ 'run', 'build', 'devel' ],
|
||||
scope = Scope.Subtree, add_self=True, names_only=True)
|
||||
out = []
|
||||
for m in deps:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class CmdLdflags(Cmd): # export
|
|||
|
||||
# -L needs to contain more paths than libs linked with -l would require
|
||||
def __get_ldpathflags(self, names: list[str], exclude: list[str] = []) -> str:
|
||||
deps = self.app.get_modules_from_project_txt(names, ['pkg.requires.jw'], 'build',
|
||||
deps = self.app.get_project_refs(names, ['pkg.requires.jw'], 'build',
|
||||
scope = Scope.Subtree, add_self=True, names_only=True)
|
||||
ret = []
|
||||
for m in deps:
|
||||
|
|
@ -38,7 +38,7 @@ class CmdLdflags(Cmd): # export
|
|||
return(' '.join(ret))
|
||||
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
deps = self.app.get_modules_from_project_txt(args.module, ['pkg.requires.jw'], 'build',
|
||||
deps = self.app.get_project_refs(args.module, ['pkg.requires.jw'], 'build',
|
||||
scope = Scope.One, add_self=args.add_self, names_only=True)
|
||||
out = []
|
||||
for m in reversed(deps):
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class CmdLdlibpath(Cmd): # export
|
|||
parser.add_argument('module', nargs='*', help='Modules')
|
||||
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
deps = self.app.get_modules_from_project_txt(args.module, ['pkg.requires.jw'], [ 'run', 'build', 'devel' ],
|
||||
deps = self.app.get_project_refs(args.module, ['pkg.requires.jw'], [ 'run', 'build', 'devel' ],
|
||||
scope = Scope.Subtree, add_self=True, names_only=True)
|
||||
out = []
|
||||
for m in deps:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class CmdPath(Cmd): # export
|
|||
parser.add_argument('module', nargs='*', help='Modules')
|
||||
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
deps = self.app.get_modules_from_project_txt(args.module, ['pkg.requires.jw'], 'run',
|
||||
deps = self.app.get_project_refs(args.module, ['pkg.requires.jw'], 'run',
|
||||
scope = Scope.Subtree, add_self=True, names_only=True)
|
||||
out = []
|
||||
for m in deps:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,6 @@ class CmdPrereq(Cmd): # export
|
|||
parser.add_argument('module', nargs='*', help='Modules')
|
||||
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
deps = self.app.get_modules_from_project_txt(args.module, ['pkg.requires.jw'],
|
||||
deps = self.app.get_project_refs(args.module, ['pkg.requires.jw'],
|
||||
args.flavour, scope = Scope.Subtree, add_self=False, names_only=True)
|
||||
print(' '.join(deps))
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class CmdPythonpath(Cmd): # export
|
|||
p.add_argument('module', help='Modules', nargs='*')
|
||||
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
deps = self.app.get_modules_from_project_txt(args.module, ['pkg.requires.jw'], [ 'run', 'build' ],
|
||||
deps = self.app.get_project_refs(args.module, ['pkg.requires.jw'], [ 'run', 'build' ],
|
||||
scope = Scope.Subtree, add_self=True, names_only=True)
|
||||
out = []
|
||||
for m in deps:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class CmdPythonpathOrig(Cmd): # export
|
|||
parser.add_argument('module', nargs='*', help='Modules')
|
||||
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
deps = self.app.get_modules_from_project_txt(args.module, ['pkg.requires.jw'], [ 'run', 'build' ],
|
||||
deps = self.app.get_project_refs(args.module, ['pkg.requires.jw'], [ 'run', 'build' ],
|
||||
scope = Scope.Subtree, add_self=True, names_only=True)
|
||||
r = ''
|
||||
for m in deps:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Iterable
|
||||
from argparse import Namespace, ArgumentParser
|
||||
|
||||
from ...App import Scope
|
||||
|
|
@ -29,7 +30,7 @@ class CmdRequiredOsPkg(Cmd): # export
|
|||
# TODO: This adds too much. Only the run dependencies of the build dependencies would be needed.
|
||||
flavours.append('run')
|
||||
log(DEBUG, "flavours = " + args.flavours)
|
||||
deps = self.app.get_modules_from_project_txt(modules, ['pkg.requires.jw'], flavours,
|
||||
deps = self.app.get_project_refs(modules, ['pkg.requires.jw'], flavours,
|
||||
scope = Scope.Subtree, add_self=True, names_only=True)
|
||||
if args.skip_excluded:
|
||||
for d in deps:
|
||||
|
|
@ -38,15 +39,12 @@ class CmdRequiredOsPkg(Cmd): # export
|
|||
subsecs = self.app.os_cascade()
|
||||
log(DEBUG, "subsecs = ", subsecs)
|
||||
requires = []
|
||||
for s in subsecs:
|
||||
for f in flavours:
|
||||
vals = self.app.collect_values(deps, 'pkg.requires.' + s, f)
|
||||
for sec in subsecs:
|
||||
for flavour in flavours:
|
||||
vals = self.app.get_values(deps, ['pkg.requires.' + sec], [flavour])
|
||||
if vals:
|
||||
requires = requires + vals
|
||||
if args.quote:
|
||||
requires = [f'"{dep}"' for dep in requires]
|
||||
# TODO: add all not in build tree as -devel
|
||||
r = ''
|
||||
for m in requires:
|
||||
r = r + ' ' + m
|
||||
print(r[1:])
|
||||
print(' '.join(requires))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue