Everywhere: Rename package "jw-build" to "jw-pkg"

jw-build doesn't stop at building software, packaging it afterwards
is also a core feature, so this commit gives the package a better
name.

The commit replaces strings s/jw-build/jw-pkg/ in text files and file
names. Fallout to the functionality is fixed, variable names are left
as they are, though. To be adjusted by later commits.

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2025-11-26 17:48:01 +01:00
commit 9217d38964
50 changed files with 36 additions and 36 deletions

2
.gitignore vendored
View file

@ -4,7 +4,7 @@ bin/*.py
*.done
*.dist
make/.cache.mk
make/jw-build.pc
make/jw-pkg.pc
minimal-to-master.sh
pkg-build.dist
pkgconfig/

View file

@ -244,7 +244,7 @@ else ifeq ($(COMPILER_SUITE),clang)
endif # COMPILER_SUITE
# -- jw-build variables based on arch and toolchain
# -- jw-pkg variables based on arch and toolchain
ifeq ($(TARGET_TRIPLET),arm-none-eabi)
LIBTYPE ?= static
@ -514,7 +514,7 @@ ifeq ($(USE_PROJECT_LIB),true)
PROJECTLIB_LDFLAGS += -L$(TOPDIR)/lib -l$(LIBNAME)
endif
ifneq ($(filter jw-build,$(PREREQ_BUILD)),)
ifneq ($(filter jw-pkg,$(PREREQ_BUILD)),)
ifeq ($(TARGET),mingw)
JW_FINAL_LDFLAGS += -lglib-2.0 -lws2_32 -lstdc++
endif

View file

@ -1,4 +1,4 @@
# jw-build library
# jw-pkg library
# (c) 2001 jannet it services
# contact@jannet.de
# $Id$

View file

@ -1,4 +1,4 @@
# jw-build library
# jw-pkg library
# (c) 2001 jannet it services
# contact@jannet.de
# $Id$

View file

@ -9,7 +9,7 @@ ifeq ($(USE_USER_URL),true)
endif
SUBMOD_SRC_DIR ?= .
GIT_SRV_ADMIN_SH = ssh $(JANWARE_USER_PREFIX)git.janware.com /opt/jw-build/bin/git-srv-admin.sh
GIT_SRV_ADMIN_SH = ssh $(JANWARE_USER_PREFIX)git.janware.com /opt/jw-pkg/bin/git-srv-admin.sh
GIT_REPO_URL = ssh://$(JANWARE_USER_PREFIX)git.janware.com/srv/git/$(JANWARE_USER)/contrib/$(SUBMOD_PKG_NAME)
SUBMOD_PATH = $(patsubst $(abspath $(TOPDIR))/%,%,$(abspath $(CWD)/$(SUBMOD_SRC_DIR)))
SUB_GIT = git -C $(SUBMOD_SRC_DIR)

View file

@ -149,7 +149,7 @@ else
endif
SYSTEM_LIBDIR = /usr/$(SYSTEM_LIBDIR_NAME)
# - support legacy jw-build target variables
# - support legacy jw-pkg target variables
ifneq ($(findstring $(TARGET_ABI),mingw mingw32 mingw64),)
TARGET ?= mingw
else

View file

@ -5,7 +5,7 @@
# (C) Copyright 2001-2025, Jan Lindemann <jan@janware.com>
#
# This is the top-level Makefile for a software build tree organized by
# jw-build. It is provided under the terms of the GNU Lesser Public License,
# jw-pkg. It is provided under the terms of the GNU Lesser Public License,
# Version 2.
#
# Current documentation on how this Makefile is meant to be used can be found
@ -17,7 +17,7 @@ PROJECTS_MAKEFILE_NAME := $(firstword $(MAKEFILE_LIST))
-include local.mk
JWBDIR ?= jw-build
JWBDIR ?= jw-pkg
ifeq ($(origin PROJECTS_DIR_REMOTE_BASE),undefined)
ifneq ($(wildcard $(JWBDIR)),)

View file

@ -5,7 +5,7 @@
# (C) Copyright 2001-2025, Jan Lindemann <jan@janware.com>
#
# This is the top-level Makefile for a software build tree organized by
# jw-build. It is provided under the terms of the GNU Lesser Public License,
# jw-pkg. It is provided under the terms of the GNU Lesser Public License,
# Version 2.
#
# Current documentation on how this Makefile is meant to be used can be found
@ -21,13 +21,13 @@ PROJECTS_MAKEFILE_NAME ?= $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)
# -- Find JWBDIR
DEV_PROJECTS_DIR ?= .
JWBDIR_NAME ?= jw-build
JWBDIR_NAME ?= jw-pkg
JWBDIR_SEARCH_PATH ?= $(DEV_PROJECTS_DIR) $(BUILD_TOOLS_PREFIX)/opt/$(FLAVOUR_PATH_PREFIX)
JWBDIR ?= $(firstword $(wildcard $(addsuffix /$(JWBDIR_NAME),$(JWBDIR_SEARCH_PATH))))
JW_BUILD_BINDIR = $(JWBDIR)/bin
JWB_SCRIPT_DIR = $(firstword $(wildcard ./$(JWBDIR_NAME)/scripts $(JW_BUILD_BINDIR)) jwb-script-dir-not-found)
JW_BUILD_REMOTE_BINDIR = /opt/jw-build/bin
JW_BUILD_REMOTE_BINDIR = /opt/jw-pkg/bin
SHELL = /bin/bash -o pipefail +H
PROJECTS_TXT ?= projects.txt
@ -89,7 +89,7 @@ else
endif
ifeq ($(filter pkg-%,$(MAKECMDGOALS)),)
JW_BUILD_SSH_EXTRA_OPTS += -o StrictHostKeyChecking=no -o ControlMaster=auto -o ControlPath=/tmp/%r@jw-build:%h:%p -o ControlPersist=3m
JW_BUILD_SSH_EXTRA_OPTS += -o StrictHostKeyChecking=no -o ControlMaster=auto -o ControlPath=/tmp/%r@jw-pkg:%h:%p -o ControlPersist=3m
ifneq ($(JANWARE_USER),)
JW_BUILD_SSH_EXTRA_OPTS += -l $(JANWARE_USER)
endif
@ -137,7 +137,7 @@ endif
PURGE_SH = /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/purge-stale-projects.sh $(JW_BUILD_BINDIR)/purge-stale-projects.sh) purge-not-found)
PKG_MANAGER_SH ?= /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/pkg-manager.sh $(JW_BUILD_BINDIR)/pkg-manager.sh) pkg-manager-not-found)
CREATE_PROJECT_SH ?= /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/jw-build-create-project.sh $(JW_BUILD_BINDIR)/jw-build-create-project.sh) jw-build-create-project-not-found)
CREATE_PROJECT_SH ?= /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/jw-pkg-create-project.sh $(JW_BUILD_BINDIR)/jw-pkg-create-project.sh) jw-pkg-create-project-not-found)
LIST_VCS_FILES_SH = /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/scm.sh $(JW_BUILD_BINDIR)/scm.sh) scm-sh-not-found) ls-files
GIT_SRV_ADMIN_SH = JW_BUILD_SSH_EXTRA_OPTS="$(JW_BUILD_SSH_EXTRA_OPTS)" $(GIT_SSH) $(JANWARE_USER)@git.janware.com $(JW_BUILD_REMOTE_BINDIR)/git-srv-admin.sh

View file

@ -21,7 +21,7 @@ GIT_DESCR = $(TOPDIR)/.git/description
GIT_MAIN_BRANCH ?= master
OPT_JANWARE_PROJECT ?= -j
PKG_MANAGER_SH ?= /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/pkg-manager.sh /opt/jw-build/bin/pkg-manager.sh) pkg-manager-not-found)
PKG_MANAGER_SH ?= /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/pkg-manager.sh /opt/jw-pkg/bin/pkg-manager.sh) pkg-manager-not-found)
ifeq ($(OPT_JANWARE_PROJECT),-j)
REMOTE_GIT_FLAVOUR ?= proj
@ -144,7 +144,7 @@ git-config:
git remote -v | grep -q "^origin *$(REMOTE_GIT_URL)" || git remote set-url --push origin $(REMOTE_GIT_URL)
git-clone-to-remote:
ssh $(JANWARE_USER)@git.janware.com "/opt/jw-build/bin/git-srv-admin.sh $(OPT_JANWARE_PROJECT) create-personal-project $(PROJECT)"
ssh $(JANWARE_USER)@git.janware.com "/opt/jw-pkg/bin/git-srv-admin.sh $(OPT_JANWARE_PROJECT) create-personal-project $(PROJECT)"
make git-config
git push --set-upstream origin $(GIT_MAIN_BRANCH)
git push --all $(REMOTE_GIT_URL)
@ -160,7 +160,7 @@ git-init-remote: git-import git-clone-to-remote
git-descr: $(GIT_DESCR)
git-update-project-description:
ssh $(JANWARE_USER)@git.janware.com "/opt/jw-build/bin/git-srv-admin.sh $(OPT_JANWARE_PROJECT) update-descriptions $(PROJECT)"
ssh $(JANWARE_USER)@git.janware.com "/opt/jw-pkg/bin/git-srv-admin.sh $(OPT_JANWARE_PROJECT) update-descriptions $(PROJECT)"
projects-%:
$(PYTHON) $(JWB_SCRIPT_DIR)/jw-projects.py build $* $(PROJECT)

View file

@ -0,0 +1,197 @@
#!/bin/bash
log()
{
echo $@
}
err()
{
log $@
}
fatal()
{
err $@
exit 1
}
usage()
{
echo "$myname [-h] [-s summary] [-d description] [-g pkg-group] name"
[ "$1" ] && exit $1
}
cat_Makefile()
{
cat <<-EOT
TOPDIR = .
include \$(TOPDIR)/make/proj.mk
include \$(JWBDIR)/make/topdir.mk
EOT
}
cat_VERSION()
{
cat <<-EOT
1.0.0-0-dev
EOT
}
cat_make_Makefile()
{
cat <<-EOT
TOPDIR = ..
include \$(TOPDIR)/make/proj.mk
include \$(JWBDIR)/make/make.mk
EOT
}
cat_make_proj_mk()
{
cat <<-EOT
# to be included from inside the project directory
DEV_PROJECTS_DIR ?= \$(TOPDIR)/..
ifeq (\$(TARGET),mingw)
FLAVOUR_PATH_PREFIX = win32/
endif
JWBDIR ?= \$(firstword \$(wildcard \$(DEV_PROJECTS_DIR)/jw-pkg \$(BUILD_TOOLS_PREFIX)/opt/\$(FLAVOUR_PATH_PREFIX)jw-pkg))
USE_PROJECT_LIB = false
HDRDIR_SCOPE_SUFFIX = \$(PROJECT)
EOT
}
cat_make_project_conf()
{
local login=$JANWARE_USER
[ "$login" ] || login=`whoami`
local fullname=`getent passwd $login | cut -d: -f5`
[ "$fullname" ] || fullname=$JANWARE_USER
[ "$fullname" ] || fullname=`whoami | xargs getent passwd | cut -d: -f5`
[ "$fullname" ] || fullname=`whoami`
cat <<-EOT
[summary]
$summary
[description]
$description
[global]
group = "$pkg_group"
subpackages = $subpackages
license = Copyright (c) $fullname, all rights reserved
jw-maintainer = $JANWARE_USER
[build]
libname = none
[pkg.requires.jw]
devel = $name-run = VERSION-REVISION, jw-pkg-devel = VERSION
build = jw-pkg-devel
EOT
}
cat_gitignore()
{
cat <<-EOT
*.done
*.dist
*.dep.mk
.cache.mk
*.o
*.so.*
*.so
*.a
*.rep
ld-*.conf
*.ldscript
*.pc
$name
*_generated_*
*.secret
local.mk
.gdb_history
dist
mkspec.sh
*.out
*.orig
*.old
.feedfs_history
.exrc
*.swp
*tmp*
__init__.py
__pycache__
EOT
}
# ---------------------- here we go
myname=`basename $0`
projdir=.
summary=
description=
subpackages="run"
eval set -- `getopt -- hd:s:p:g: "$@"`
while [ "$1" != -- ]; do
case $1 in
-d)
eval description=\"$2\"
shift;;
-s)
eval summary=\"$2\"
shift;;
-p)
eval subpackages=\"$2\"
shift;;
-g)
eval pkg_group=\"$2\"
shift;;
-h)
usage 0;;
*)
usage 1;;
esac
shift
done
shift
[ -z "$description" ] && description="$summary"
[ -z "$summary" ] && summary="$description"
[ -z "$pkg_group" ] && pkg_group="Amusements/Teaching/Other"
[ -f $projdir/Makefile ] || {
fatal "$projdir doesn't contain a Makefile"
}
name="$1"
[ -d $projdir/$name ] && {
fatal "$projdir/$name already exists"
}
set -e
mkdir -p $projdir
mkdir $name
cd $projdir/$name
mkdir make
cat_Makefile > Makefile
cat_make_Makefile > make/Makefile
cat_VERSION > VERSION
cat_make_proj_mk > make/proj.mk
cat_make_project_conf > make/project.conf
cat_gitignore > .gitignore
log Successfully created project template \"$name\".

View file

@ -1,6 +1,6 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from jw.build.App import App
from jw.pkg.App import App
App().run()

View file

@ -142,7 +142,7 @@ clone()
local toref="${refspec[2]}"
[ "$fromuser" ] || fromuser=$whoami
[ "$fromref" ] || fromref=master
local git_srv_admin="$SSH $login@git.janware.com /opt/jw-build/bin/git-srv-admin.sh"
local git_srv_admin="$SSH $login@git.janware.com /opt/jw-pkg/bin/git-srv-admin.sh"
if [ -z "$projects" ]; then
projects=`$jw_projects list-repos --from-user $fromuser $remote_base`

View file

@ -547,7 +547,7 @@ upload_file()
;;
*)
target=$target_base/rpm/$t/$f:$upload_attrib
#/srv/ftp/pub/packages/jw-foss/suse/tumbleweed/rpm/src/jw-build-1.0.0-74.src.rpm
#/srv/ftp/pub/packages/jw-foss/suse/tumbleweed/rpm/src/jw-pkg-1.0.0-74.src.rpm
;;
esac
;;
@ -559,7 +559,7 @@ upload_file()
;;
*)
target=$target_base/rpm/$t/$f:$upload_attrib
#/srv/ftp/pub/packages/jw-foss/suse/tumbleweed/rpm/x86_64/jw-build-run-1.0.0-74.x86_64.rpm
#/srv/ftp/pub/packages/jw-foss/suse/tumbleweed/rpm/x86_64/jw-pkg-run-1.0.0-74.x86_64.rpm
;;
esac
esac

View file

@ -58,7 +58,7 @@ done
local_cvs_proj=`ls -d */CVS 2>/dev/null | sed 's%/[^/]*%%'`
local_git_proj=`ls -d */.git 2>/dev/null | sed 's%/[^/]*%%'`
remote_cvs_proj=`$ssh cvs.janware.com find /srv/cvs/proj -maxdepth 1 -mindepth 1 -type d -executable -readable | sed 's%.*/%%'`
remote_git_proj=`$ssh git.janware.com /opt/jw-build/bin/git-srv-admin.sh -j list-personal-projects`
remote_git_proj=`$ssh git.janware.com /opt/jw-pkg/bin/git-srv-admin.sh -j list-personal-projects`
date=`date +'%Y%m%d'`
myname=`basename $0`

466
src/python/jw/pkg/App.py Normal file
View file

@ -0,0 +1,466 @@
# -*- coding: utf-8 -*-
#
# This source code file is a merge of various build tools and a horrible mess.
#
import os, sys, argparse, pwd, re
# 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
class ResultCache(object):
def __init__(self):
self.__cache = {}
def run(self, func, args):
d = self.__cache
depth = 0
keys = [ func.__name__ ] + args
l = len(keys)
for k in keys:
if k is None:
k = 'None'
else:
k = str(k)
depth += 1
#self.projects.debug('depth = ', depth, 'key = ', k, 'd = ', str(d))
if k in d:
if l == depth:
return d[k]
d = d[k]
continue
if l == depth:
r = func(*args)
d[k] = r
return r
d = d[k] = {}
#d = d[k]
raise Exception("cache algorithm failed for function", func.__name__, "in depth", depth)
# ----------------------------------------------------------------- class App
class App(object):
def __init__(self):
self.global_args = []
self.opt_os = None
self.top_name = None
self.glob_os_cascade = None
self.dep_cache = {}
self.my_dir = os.path.dirname(os.path.realpath(__file__))
self.opt_debug = False
self.res_cache = ResultCache()
self.topdir = None
self.projs_root = os.path.expanduser("~") + '/local/src/jw.dev/proj'
def debug(self, *objs):
if self.opt_debug:
print("DEBUG: ", *objs, file = sys.stderr)
def warn(self, *objs):
print("WARNING: ", *objs, file = sys.stderr)
def err(self, *objs):
print("ERR: ", *objs, file = sys.stderr)
def proj_dir(self, name):
if name == self.top_name:
return self.topdir
for d in [ self.projs_root, '/opt' ]:
r = d + '/' + name
if os.path.exists(r):
return r
if os.path.exists(f'/usr/share/doc/packages/{name}/VERSION'):
# The package exists but does not have a dedicated project directory
return None
raise Exception('No project path found for module "{}"'.format(name))
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))]
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:
self.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):
pd = self.proj_dir(name)
if pd is None:
return None
for r in [ pd + "/tools/html/htdocs", pd + "/htdocs", "/srv/www/proj/" + name ]:
if os.path.isdir(r):
return r
return None
def os_cascade(self):
import platform
if self.glob_os_cascade is not None:
return self.glob_os_cascade.copy()
r = [ 'os', platform.system().lower() ]
os = self.opt_os if self.opt_os is not None else self.res_cache.run(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.glob_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()
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:
#self.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)
#self.debug(" returning", rr)
return rr
try:
#self.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:
self.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):
self.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
self.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:
self.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.res_cache.run(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(",")])
# scope 0: no children
# scope 1: children
# scope 2: recursive
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, visited, spec, section, key, add_self, scope,
names_only):
name = self.strip_module_from_spec(spec)
if names_only:
spec = name
if spec in buf:
return
if spec in visited:
if add_self:
buf.append(spec)
return
visited.add(spec)
deps = self.get_value(name, section, key)
self.debug("name = ", name, "section = ", section, "key = ", key, "deps = ", deps, "scope = ", scope, "visited = ", visited)
if deps and scope > 0:
if scope == 1:
subscope = 0
else:
subscope = 2
deps = deps.split(',')
for dep in deps:
dep = dep.strip()
if not(len(dep)):
continue
self.add_modules_from_project_txt_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):
if isinstance(keys, str):
keys = [ keys ]
#r = set()
r = []
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)
# TODO: this looks like a performance hogger
for m in rr:
if not m in r:
r.append(m)
return r
def get_libname(self, names):
vals = self.get_modules_from_project_txt(names, ['build'], 'libname',
scope = 1, add_self=False, names_only=True)
if not vals:
return ' '.join(names)
if 'none' in vals:
vals.remove('none')
return ' '.join(reversed(vals))
def is_excluded_from_build(self, module):
self.debug("checking if module " + module + " is excluded from build")
exclude = self.get_modules_from_project_txt([ module ], ['build'], 'exclude',
scope = 1, add_self=False, names_only=True)
cascade = self.os_cascade() + [ 'all' ]
for p1 in exclude:
for p2 in cascade:
if p1 == p2:
return p1
return None
# -L needs to contain more paths than libs linked with -l would require
def get_ldpathflags(self, names, exclude = []):
deps = self.get_modules_from_project_txt(names, ['pkg.requires.jw'], 'build',
scope = 2, add_self=True, names_only=True)
r = ''
for m in deps:
if m in exclude:
continue
libname = self.get_libname([m])
if len(libname):
r = r + ' -L' + self.proj_dir(m) + '/lib'
print(r[1:])
def get_ldflags(self, names, exclude = [], add_self_ = False):
#print(names)
deps = self.get_modules_from_project_txt(names, ['pkg.requires.jw'], 'build',
scope = 1, add_self=add_self_, names_only=True)
self.debug("deps = " + ' '.join(deps))
#print(deps)
r = ''
for m in reversed(deps):
if m in exclude:
continue
libname = self.get_libname([m])
if len(libname):
#r = r + ' -L' + self.proj_dir(m) + '/lib -l' + libname
r = r + ' -l' + libname
if len(r):
ldpathflags = self.get_ldpathflags(names, exclude)
if ldpathflags:
r = ldpathflags + ' ' + r
return r[1::]
return ''
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 = 1, 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:
self.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)
# -------------------------------------------------------------------- here we go
def run(self):
skip = 0
for a in sys.argv[1::]:
self.global_args.append(a)
if a in [ '-p', '--prefix', '-t', '--topdir', '-O', '--os' ]:
skip = 1
continue
if skip > 0:
skip = skip -1
continue
if a[0] != '-':
break
# -- defaults
self.projs_root = pwd.getpwuid(os.getuid()).pw_dir + "/local/src/jw.dev/proj"
parser = argparse.ArgumentParser(description='Project metadata evaluation')
parser.add_argument('-d', '--debug', action='store_true',
default=False, help='Output debug information to stderr')
parser.add_argument('-t', '--topdir', nargs=1, default = [], help='Project Path')
parser.add_argument('-p', '--prefix', nargs=1, default = [ self.projs_root ], help='App Path Prefix')
parser.add_argument('-O', '--os', nargs=1, default = [], help='Target operating system')
parser.add_argument('cmd', default='', help=f'Command, run "{sys.argv[0]} commands" for a list of supported commands')
parser.add_argument('arg', nargs='*', help='Command arguments')
args = parser.parse_args(self.global_args)
self.opt_debug = args.debug
if len(args.os):
self.opt_os = args.os[0]
self.debug("----------------------------------------- running ", ' '.join(sys.argv))
self.projs_root = args.prefix[0]
self.debug("projs_root = ", self.projs_root)
if len(args.topdir):
self.topdir = args.topdir[0]
if self.topdir:
self.top_name = self.res_cache.run(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)))
import importlib
cmd_name = args.cmd.replace('-', '_')
cc_cmd_name = 'Cmd' + ''.join(x.capitalize() for x in cmd_name.lower().split("_"))
module = importlib.import_module('jw.pkg.build.cmds.' + cc_cmd_name)
cmd = getattr(module, cc_cmd_name)()
cmd.app = self
subparser = argparse.ArgumentParser(description=cmd_name)
cmd.add_arguments(subparser)
args = subparser.parse_args(sys.argv[(len(self.global_args) + 1)::])
try:
return cmd.run(args)
except Exception as e:
self.err('Failed to run >{}<: {}'.format(' '.join(sys.argv), e))
raise
sys.exit(1)

View file

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import Type, Union, TypeVar
import inspect, abc, argparse
from argparse import ArgumentParser
class Cmd(abc.ABC): # export
def __init__(self, name: str, help: str) -> None:
from ..App import App
self.name = name
self.help = help
self.parent = None
self.children: list[Cmd] = []
self.child_classes: list[Type[Cmd]] = []
self.app: App|None = None
@abc.abstractmethod
def _run(self, args):
pass
def run(self, args):
return self._run(args)
def add_parser(self, parsers) -> ArgumentParser:
r = parsers.add_parser(self.name, help=self.help,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
r.set_defaults(func=self.run)
return r
def add_subcommands(self, cmd: Union[str, Type[Cmd], list[Type[Cmd]]]) -> None:
if isinstance(cmd, str):
import sys, re
sc = []
for name, obj in inspect.getmembers(sys.modules[self.__class__.__module__]):
if inspect.isclass(obj):
if re.search(cmd, str(obj)):
sc.append(obj)
log.slog(log.DEBUG, f"Found subcommand {obj}")
self.add_subcommands(sc)
return
if isinstance(cmd, list):
for c in cmd:
self.add_subcommands(c)
return
self.child_classes.append(cmd)
# To be overridden by derived class in case the command does take arguments.
# Will be called from App base class constructor and set up the parser hierarchy
def add_arguments(self, parser: ArgumentParser) -> None:
pass
def conf_value(self, path, default=None):
ret = None if self.app is None else self.app.conf_value(path, default)
if ret is None and default is not None:
return default
return ret

View file

@ -0,0 +1,5 @@
TOPDIR = ../../../../..
PY_UPDATE_INIT_PY = false
include $(TOPDIR)/make/proj.mk
include $(JWBDIR)/make/py-mod.mk

View file

View file

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
import re
from argparse import Namespace, ArgumentParser
from ..Cmd import Cmd
class BaseCmdPkgRelations(Cmd):
def pkg_relations(self, rel_type, args):
version_pattern=re.compile("[0-9-.]*")
if args.subsections is None:
subsecs = self.app.os_cascade()
subsecs.append('jw')
else:
subsecs = args.subsections.split(',')
self.app.debug('flavour = ', args.flavour, ', subsecs = ', ' '.join(subsecs))
ignore = args.ignore.split(',')
self.app.debug("ignore = ", ignore)
r = []
flavours = args.flavour.split(',')
for flavour in flavours:
for s in subsecs:
section = 'pkg.' + rel_type + '.' + s
visited = set()
modules = args.module.copy()
while len(modules):
m = modules.pop(0)
if m in visited or m in ignore:
continue
visited.add(m)
value = self.app.get_value(m, section, flavour)
if not value:
continue
deps = value.split(',')
for spec in deps:
dep = re.split('([=><]+)', spec)
if args.no_version:
dep = dep[:1]
dep = list(map(str.strip, dep))
dep_name = re.sub('-dev$|-devel$|-run$', '', dep[0])
if dep_name in ignore or dep[0] in ignore:
continue
if args.no_subpackages:
dep[0] = dep_name
for i, item in enumerate(dep):
dep[i] = item.strip()
if s == 'jw':
if args.recursive and not dep_name in visited and not dep_name in modules:
modules.append(dep_name)
if len(dep) == 3:
if args.dont_expand_version_macros and dep_name in args.module:
version = dep[2]
else:
version = self.app.get_value(dep_name, 'version', '')
if dep[2] == 'VERSION':
if args.dont_strip_revision:
dep[2] = version
else:
dep[2] = version.split('-')[0]
elif dep[2] == 'VERSION-REVISION':
dep[2] = version
elif version_pattern.match(dep[2]):
# dep[2] = dep[2]
pass
else:
raise Exception("Unknown version specifier in " + spec)
cleaned_dep = ' '.join(dep)
if not cleaned_dep in r:
self.app.debug("appending", cleaned_dep)
r.append(cleaned_dep)
return args.delimiter.join(r)
def print_pkg_relations(self, rel_type, args_):
print(self.pkg_relations(rel_type, args_))
def __init__(self, relation: str, help: str) -> None:
super().__init__('pkg-' + relation, help=help)
self.relation = relation
def add_arguments(self, parser: ArgumentParser) -> None:
super().add_arguments(parser)
parser.add_argument('-S', '--subsections', nargs='?', default=None, help='Subsections to consider, comma-separated')
parser.add_argument('-d', '--delimiter', nargs='?', default=', ', help='Output words delimiter')
parser.add_argument('flavour', help='Flavour')
parser.add_argument('module', nargs='*', help='Modules')
parser.add_argument('-p', '--no-subpackages', action='store_true',
default=False, help='Cut -run and -devel from package names')
parser.add_argument('--no-version', action='store_true',
default=False, help='Don\'t report version information')
parser.add_argument('--dont-strip-revision', action='store_true',
default=False, help='Always treat VERSION macro as VERSION-REVISION')
parser.add_argument('--recursive', action='store_true',
default=False, help='Find dependencies recursively')
parser.add_argument('--dont-expand-version-macros', action='store_true',
default=False, help='Don\'t expand VERSION and REVISION macros')
parser.add_argument('--ignore', nargs='?', default='', help='Packages that '
'should be ignored together with their dependencies')
def _run(self, args: Namespace) -> None:
return self.print_pkg_relations(self.relation, args)

View file

@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
import os, re, sys, subprocess, datetime
from argparse import Namespace, ArgumentParser
from ..Cmd import Cmd
class CmdBuild(Cmd): # export
def __init__(self) -> None:
super().__init__('build', help='janware software project build tool')
def add_arguments(self, parser: ArgumentParser) -> None:
super().add_arguments(parser)
parser.add_argument('--exclude', default='', help='Space seperated ist of modules to be excluded from build')
parser.add_argument('-n', '--dry-run', action='store_true',
default=False, help='Don\'t build anything, just print what would be done.')
parser.add_argument('-O', '--build-order', action='store_true',
default=False, help='Don\'t build anything, just print the build order.')
parser.add_argument('-I', '--ignore-deps', action='store_true',
default=False, help='Don\'t build dependencies, i.e. build only modules specified on the command line')
parser.add_argument('target', default='all', help='Build target')
parser.add_argument('modules', nargs='+', default='', help='Modules to be built')
def _run(self, args: Namespace) -> None:
def read_deps(cur, prereq_type):
# dep cache doesn't make a difference at all
if prereq_type in self.app.dep_cache:
if cur in self.app.dep_cache[prereq_type]:
return self.app.dep_cache[prereq_type][cur]
else:
self.app.dep_cache[prereq_type] = {}
r = self.app.get_modules_from_project_txt([ cur ], ['pkg.requires.jw'],
prereq_type, scope = 2, add_self=False, names_only=True)
self.app.debug('prerequisites = ' + ' '.join(r))
if cur in r:
r.remove(cur)
self.app.debug('inserting', prereq_type, "prerequisites of", cur, ":", ' '.join(r))
self.app.dep_cache[prereq_type][cur] = r
return r
def read_deps_cached(cur, prereq_type):
return self.app.res_cache.run(read_deps, [ cur, prereq_type ])
def add_dep_tree(cur, prereq_types, tree, all_deps):
self.app.debug("adding prerequisites " + ' '.join(prereq_types) + " of module " + cur)
if cur in all_deps:
self.app.debug('already handled module ' + cur)
return 0
deps = set()
all_deps.add(cur)
for t in prereq_types:
self.app.debug("checking prereqisites of type " + t)
deps.update(read_deps_cached(cur, t))
for d in deps:
add_dep_tree(d, prereq_types, tree, all_deps)
tree[cur] = deps
return len(deps)
def calculate_order(order, modules, prereq_types):
all_deps = set()
dep_tree = {}
for m in modules:
self.app.debug("--- adding dependency tree of module " + m)
add_dep_tree(m, prereq_types, dep_tree, all_deps)
while len(all_deps):
# Find any leaf
for d in all_deps:
if not len(dep_tree[d]): # Dependency d doesn't have dependencies itself
break # found
else: # no Leaf found
print(all_deps)
raise Exception("fatal: the dependencies between these modules are unresolvable")
order.append(d) # do it
# bookkeep it
all_deps.remove(d)
for k in dep_tree.keys():
if d in dep_tree[k]:
dep_tree[k].remove(d)
return 1
def run_make(module, target, cur_project, num_projects):
#make_cmd = "make " + target + " 2>&1"
make_cmd = [ "make", target ]
path = self.app.proj_dir(module)
delim_len = 120
delim = '---- [%d/%d]: running %s in %s -' % (cur_project, num_projects, make_cmd, path)
delim = delim + '-' * (delim_len - len(delim))
print(',' + delim + ' >')
patt = self.app.is_excluded_from_build(module)
if patt is not None:
print('| Configured to skip build on platform >' + patt + '<')
print('`' + delim + ' <')
return
os.chdir(path)
p = subprocess.Popen(make_cmd, shell=False, stdout=subprocess.PIPE, stderr=None, close_fds=True)
for line in iter(p.stdout.readline, b''):
line = line.decode(sys.stdout.encoding)
sys.stdout.write('| ' + line) # avoid extra newlines from print()
sys.stdout.flush()
p.wait()
print('`' + delim + ' <')
if p.returncode:
print(' '.join(make_cmd) + ' failed')
raise Exception(time.strftime("%Y-%m-%d %H:%M") + ": failed to make target " + target + " in module " + module + " below base " + self.app.projs_root)
def run_make_on_modules(modules, order, target):
cur_project = 0
num_projects = len(order)
if target in ["clean", "distclean"]:
for m in reversed(order):
cur_project += 1
run_make(m, target, cur_project, num_projects)
if m in modules:
modules.remove(m)
if not len(modules):
print("all modules cleaned")
return
else:
for m in order:
cur_project += 1
run_make(m, target, cur_project, num_projects)
def run(args):
self.app.debug("----------------------------------------- running ", ' '.join(sys.argv))
modules = args.modules
exclude = args.exclude.split()
target = args.target
env_exclude = os.getenv('BUILD_EXCLUDE', '')
if len(env_exclude):
print("exluding modules from environment: " + env_exclude)
exclude += " " + env_exclude
# -- build
order = []
glob_prereq_types = [ "build" ]
if re.match("pkg-.*", target) is not None:
glob_prereq_types = [ "build", "run", "release", "devel" ]
if target != 'order' and not args.build_order:
print("using prerequisite types " + ' '.join(glob_prereq_types))
print("calculating order for modules ... ")
calculate_order(order, modules, glob_prereq_types)
if args.ignore_deps:
order = [m for m in order if m in args.modules]
order = [m for m in order if m not in exclude]
if target == 'order' or args.build_order:
print(' '.join(order))
exit(0)
cur_project = 0
print("Building target %s in %d projects:" % (target, len(order)))
for m in order:
cur_project += 1
print(" %3d %s" % (cur_project, m))
if args.dry_run:
exit(0)
run_make_on_modules(modules, order, target)
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
run(args)

Some files were not shown because too many files have changed in this diff Show more