Commit graph

51 commits

Author SHA1 Message Date
c538447cc5
App.__get_project_refs(): Code beautification

In __get_project_refs():

- Rename variable dep and deps to val and vals, respectively, because that's more what they are values of key-value pairs. In some cases that can represent dependencies, in some case other things.
- Make a scope case distinction a little clearer by mentioning all possible cases in a match / case block
Signed-off-by: Jan Lindemann <jan@janware.com>
2026-06-08 20:41:37 +02:00
f687ded1a6
App.find_dir(): Allow return value None

Allow find_dir() to return None in case it couldn't find a directory, that's a legal outcome. Add a boolean parameter "throw" to support throwing an exception if the existence needs to be asserted.

It would probably be nicer for the type checkers to split this up into a throwing and non-throwing function. Postponed.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-06-01 20:22:09 +02:00
aadcdfb5f3
App.is_excluded_from_build():

App.is_excluded_from_build() uses the wrong function entirely to query the [build.exclude] section of project.conf (App.get_project_refs() instead of App.get_value()). This has obviously never worked. It rose to prominence because commit 6db73873 introduced App.__proj_dir(), which now raises an Exception if passed garbage, which in turn surfaces as

Exception: No project path found for module "debian"

Use the correct function for that: App.get_value().

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-06-01 16:11:22 +00:00
5d1ba6e15a
pyproject.toml: Enforce import annotations style

Add new ruff rules and fix their fallout:

future-annotations = true

select = [ "TC", # type-checking import placement rules "FA", # future annotations rules ]

This comprises:

- Streamline imports and exports in cmds.xxx.Cmd

- Import base class as "Base"
- Export types Cmd and Parent via __all__

- Move all types imported only for annotation below TYPE_CHECKING

- Use "from __future__ import annotations" all over the place

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-06-01 14:34:25 +02:00
6db73873e7
jw.pkg: Fix "make check" static code check fallout

The previous commits have put rules for linting and formatting via ruff, yapf, mypy and pyright into place. They are checked with the make check target, and this commit adds the fixes for the target to succeed.

It does some refactoring where type checking dug up dirty bits, and also adds lots of churn in the Python code. To a good deal, that's owed to mere formatting changes. It would have been better to seperate those from syntax and refactoring fixes into multiple commits, so that the interesting changes don't drown in the formatting nose. However, that would have been a lot of additional work only to be thrown away by later commits, hence this commit has a big diff in one piece. The size of the diff is regrettable but hopefully a one-off: What it buys is automatic format checking for CI and predictble formats for smaller diffs in the future.

Rules that "make check" enforces are, in the following order

- Syntax checkers:

- ruff check . - mypy . - pyright

- Format check:

- yapf --diff --recursive .

The refactoring includes:

- Turn the Result class into a more elaborate object, capable of doing more heavy lifting around stderr and stdout decoding, summarizing outcome, and matching error strings.
Aside from fixing broken type checks, this also removes lots of boilerplate calling code which is currently used for handling possible call outcome scenarios. Trying to access an inexistent, decoded string should raise a meaningful exception by itself now, which removes lots of code with case distinctions.

- Fix Cmd type hierarchy:

- Add the AbstractCmd class above Cmd. This is necessary because the checker rightfully complains it can't instantiate a Cmd instance where constructor arguments were needed. They never were, but the type used at the instantiating code's location in jw.pkg.App so claims.
- Lots of sub- and sub-subcommands are derived from the base class of the invoking command. That provides some properties shared across the ancestor hierarchy of a command, but is semantically unsound. Fix that by introducing jw.pkg.BaseCmd class as a place to provide basic helpers shared across all commands used in a jw.pkg.App's context, and derive all command classes from that afresh. The parent command is still reachable via a common parent property.

Formatting changes are conforming to PEP-8, mostly, with minor tweaks. All in all they include the following changes.

- Remove # -*- coding: utf-8 -*-

The line was needed by Python 2 which is not supported anylonger. For Python 3, the default encoding is UTF-8, anyway.
- Allow to run "make py-format" without having it produce any changes. It's basically "yapf --in-place --recursive ." with some code style settings, see conf/topdir/pyproject.toml. The settings may be debatable. I've had custom tweaks in place on that target, too, but then again, IDEs would have more hassle to integrate that.

- Introduce a 88 character line length limit

- One import per line, reshuffle them semantically, see [tool.isort] in pyproject.toml.

- Hide imports needed for type-checking only behind

if TYPE_CHECKING
- Spaces around assignments accounts for much churn. Having having no spaces in inline parameter list assignments and default parameter values would arguably be more compact where it's useful. On the other hand, I have not found a code formatter which allows spaces around assignments in parameter lists broken into one per line and that's often better than a wall of text.
- Add two spaces before # export, as this seems to be mandated by PEP-8

- Use single quotes by default

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-05-31 18:20:38 +02:00
c19111e5b6
App: Rename global option --uri -> --target

--uri is unnecessarily generic in that it could mean the URI of anything. --target makes it clearer that operations are to be exectuted on that target.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-24 16:53:55 +02:00
4e2b15e586
App: Support --pkg-filter / JW_DEFAULT_PKG_FILTER

Add global --pkg-filter argument, defaulting to JW_DEFAULT_PKG_FILTER. If it's specified, instantiate a PackageFilterString from it, and initialize App's Distro instance with it.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-21 21:52:09 +02:00
dacea3a104
App: Cosmetics: Replace double by single quotes

Replace " by ' where applicable for consistency.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-21 21:52:09 +02:00
aa7275f426 App.distro_xxx: Move properties to Distro.xxx

Commit a19679fec reverted the first attempt to make AsyncSSH reuse one connection during an instance lifetime. That failed because a lot of distribution-specific properties were filled in a new event loop thread started by AsyncRunner, and AsyncSSH didn't like that.

This commit is the first part of the solution: Move those properties from the App class to the Distro class, and load the Distro class in an async loader. As soon as it's instantiated, it can provide all its properties without cluttering the code with async keywords.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-19 21:00:21 +02:00
f445b196c9 App.distro_pkg_ext: Add property

Add .distro_pkg_ext to App, in order to support something along the lines of

jw-pkg distro install "https://my-company.com/repos/%{id}/%{codename}/my-base-pkg-latest.%{pkg-ext}"

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 16:07:11 +02:00
c20091d7ac App.distro_info(): Accept and return lists

App.distro_info() accepts and returns str instances, interpret anything passed as fmt parameter which is not a str as iterable, and return lists of expanded strings in that case.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 16:07:11 +02:00
ef21dc1b1e App.distro_info(): Add method

Add a method distro_info() to App, essentially providing CmdInfo's macro-expansion of platform information to other interested code.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 13:25:37 +02:00
888c0495ec lib.base: Add module

Add lib.base to provide basic definitions.

For now, move the definiions of Result, Input and InputMode from ExecContext into lib.base. Having to import them from the ExecContect module is too heavy-handed for those simple types.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 12:57:04 +02:00
d803c66f70 App.__os_release(): Use ExecContext.get()

The property App.__os_release uses _run(['cat', '/etc/os-release']), use ExecContext.get() instead as the default way to fetch content.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:26 +02:00
60630676ef App.__aexit__(): Use to close exec context

Override lib.App.__aexit() to call close() on the App.exec_context instance if it exists.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:25 +02:00
1caae611ba App.os_release: Fix debug logging

cat /etc/os-release sometimes fails over the wire, and the reason should be logged but isn't. Fix that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-11 14:50:24 +02:00
18a2ee6c99 App.get_value(): Beautify debug logging

Enclose sections / keys taken from project.conf in [square.brackets], hinting at what they are supposed to mean.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-06 16:02:27 +02:00
4393ca21fc App.os_cascade: Don't use platform.system()

Python's platform.system() outputs 'Linux', and to use it is tempting. Sadly, that's wrong, because it reflects the host's idea of the target system, not the execution context's, so replace it with straight 'linux' if the distro is known, or, failing that, the output of uname -s.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:46 +01:00
8280327602 App.os_release: cat os-release non-interactively

Even with --interactive=[true|auto], there's no point in trying to read /etc/os-release interactively, so don't do that. Most notably, this commit keeps the property method from spilling /etc/os-release's content over the terminal.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:46 +01:00
02697af568 lib.ExecContext: Align .sudo() prototype to .run()

ExecContext's .sudo() omits many of run()'s parameters, and this commit adds them. To avoid redundancy around repeating and massaging the long parameter list of both functions and their return values, it also adds some deeper changes:

- Make run(), _run(), sudo() and _sudo() always return instances of Result. Before it was allowed to return a triplet of stdout, stderr, and exit status.
- Have ExecContext stay out of the business of decoding the result entirely. Result provides a convenience method .decode() operating on stdout and stderr and leaves the decision to the caller.
This entails miniscule adaptations in calling code, namely in App.os_release, util.get_profile_env() and CmdListRepos._run().
- Wrap the _run() and _sudo() callbacks in a context manager object of type CallContext to avoid code duplication.
- Consistently name the first argument to run(), _run(), sudo() and _sudo() "cmd", not "args". The latter suggests that the caller is omitting the executable, which is not the case.
Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-20 10:30:25 +01:00
e924f34441 pkg.App.os_release: Call cat /etc/os-release

Don't open and parse /etc/os-release with Python built-in functions. Spawn "cat /etc/os-release" as a subprocess and capture the output for parsing instead. The obvious advantage is that this also works with a remote shell.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-20 10:30:25 +01:00
d905f6d555 App: Support --uri <remote-host>

Add a --uri option to App, allowing jw-pkg.py to operate over the wire.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-20 10:30:25 +01:00
277e5166e8 App.get_value(): Log return value

Log what App.get_value() lookups return with priority DEBUG, insightful for debugging.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-15 19:34:50 +01:00
5bd1f3378b App.get_value(): Beautify logging

"getting value xxx for project" is a prominent debug log message but ugly. Beautify to e.g.:

Lookup jw-fail2ban -> jw-pkg / version

Meaning project "jw-fail2ban" looks up the value for key "version" in project "jw-pkg".

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-15 10:42:35 +01:00
18c16917b2 App, .cmds.Cmd: Add .distro property

DistroBase's option --id is now redundant to the new global option --distro-id in the App class, so remove --id. The only added value DistroBase then brings to the table is its .distro property, which can be provided by App just fine at this point, given that App has all it needs to construct a Distro object, so add .distro to App and remove the entire DistroBase class.

For convenience, also make App.distro available as a newly added cmds.Cmd.distro property. This also obviates the need for the distro-related properties in the .distro.Cmd class, remove all that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-07 14:35:54 +01:00
67a2931f5e App: Support --verbose

Add the --verbose global option, which is made available as the App.verbose property.

Some functions still take a verbose parameter, but the type of these parameters is converted from bool to bool|None. The idea is that, if they are None, their verbosity falls back to the global default.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-06 19:02:22 +01:00
3e897f4df8 lib.Distro, ExecContext: Add classes, refactor lib.distro

The code below lib.distro, as left behind by the previous commit, is geared towards being directly used as a command-line API. This commit introduces the abstract base class Distro, a proxy for distribution-specific interactions. The proxy abstracts distro specifics into an API with proper method prototypes, not argparse.Namespace contents, and can thus be more easily driven by arbitrary code.

The Distro class is initialized with a member variable of type ExecContext, another new class introduced by this commit. It is designed to abstract the communication channel to the distribution instance. Currently only one specialization exists, Local, which interacts with the distribution and root file system it is running in, but is planned to be subclassed to support interaction via SSH, serial, chroot, or chains thereof.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-06 14:56:46 +01:00
f24541dbe4 pkg.App: Add options --distro-id, --interactive

Add the global options --distro-id and --interactive, and expose them as properties App.distro_id and App.interactive, respectively.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-06 12:06:28 +01:00
03fca61248 jw.pkg.App.distro_id: Don't return opensuse

get-os.sh returned "suse" for SuSE-like distros, and that seems more appropriate since SLES is not OpenSUSE but should share and ID with other SuSE variants.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-03 05:11:59 +01:00
6483de1a8f jw.pkg.App.get_os(): Don't use get-os.sh

Remove get-os.sh from App.py to reduce redunancy.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-02 12:55:36 +00:00
f58e9fe298 jw.pkg.App: Add more distro_xxx properties

In addition to the existing propert distro_id, add the properties distro_codename, distro_name, distro_cascade and distro_gnu_triplet.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-02 12:00:12 +01:00
2d08c2a12d jw.pkg.App.os_cascade: Add more fields

Add more fields to the OS cascade returned by App.os_cascade, based on the ID field in /etc/os-release. This includes some new ones, prefixed by pkg-, revealing which package format is used.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-02-15 16:13:21 +00:00
26e739ca0d src.python.jw.pkg.App: Add property distro_id

Add @property App.distro_id, returning the ID field of /etc/os-release. It is supposed to be the most specific part of the OS cascade.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-02-15 16:13:21 +00:00
a967924102 jw.pkg.App: Add property projs_root

projs_root is used read-only by CmdBuild and CmdModules, so add that property back.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-02-14 20:59:23 +01:00
eaa38113bb jw.pkg.App.get_libname(): Fix typo

get_project_refs() is invoked with projects_only=true, which should read names_only. Fix that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-02-04 16:00:45 +01:00
6ca4af77d7 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>
2026-01-29 11:32:14 +01:00
f6ed191d73 jw.pkg.App.get_modules_from_project_txt(): Add Scope

Add the Enum "Scope" to denote the scope argument of jw.pkg.App.get_modules_from_project_txt(), because it explains itself better than an integer.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
bf4834085e jw.pkg.App: Annotate add_modules_from_project_txt()

Type-annotate add_modules_from_project_txt()'s parameter list.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
5cdc32abd7 jw.pkg.App: Add lru_cache where possible

ResultCache is a home-grown result cache. The @lru_cache decorator, now available in Python 3, accomplishes the same thing, so try to ditch ResultCache for it.

Sadly, this doesn't entirely work as of now, because it uses hash() to hash the arguments, which won't work for the two list-type arguments to add_modules_from_project_txt() (buf and visited).

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
a377745e5e jw.pkg.App: Make member variables private

With the exception of top_name, which cmds.project.GetVal needs read-access to, all member variables of jw.pkg.App can be made private.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
95fa2f0d06 jw.pkg.App: Remove .debug() and friends

Replace the jw.pkg.App.debug(), .warn() and .err() methods by the global log() function. There's no obvious benefit in having App know what's logged.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
aefe983920 jw.pkg.App: Support --topdir-format

Add support for --topdir-format. The option supports several different values, affecting the console output of App wherever it knows that the output contains a reference to the projects' toplevel directory.

- "unaltered" will have it print the toplevel directory in the same format as passed to the commandline
- "absolute" will try to resolve it to an absolute path before printing

- make:XXX will return the make-varible $(XXX) instead

To implement this, the proj_dir() member function is turned into the private member function __proj_dir(), and a new member function find_dir() is supplied, with two additional parameters: search_subdirs and search_absdirs, which will try to find an existing directory relative to the toplevel directory of the given module, or in the search_absdirs list, respectively.

Command modules in cmds.projects have been updated to use the new function.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
0b83c863a2 jw.build.cmds: Move build.cmds -> cmds.projects

Reorganize the Python module structure. Placing the command classes under jw.cmds.projects instead of jw.build.cmds will allow to add a nested command structure, with the current commands, being mostly related to building software, found below a "projects" toplevel command.

Other conceivable commands could be "package" for packaging, or "distro" for commands wrapping the distribution's package manager.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
c8c5788aeb jw.pkg.App.proj_dir(): Return absolute path

Make App.proj_dir() return an absolute path. This looks like a good idea, because some of the $(call proj_query xxx) paths end up being relative, because they get proj_dir()'s idea of a directory prepended. This prohibits caching them in $(TOPDIR)/make/.cache.mk for make benefit glorious nation of performance.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
adb9837c63 jw.pkg.App: Remove --debug option

Remove the --debug option, which is superseded by the semantically richer --log-level debug in the base class.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
737f3986df jw.pkg.App: Use lib.log

Replace print() by log().

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
1f26391452 jw.pkg.cmds.Cmd: Derive from lib.Cmd

The body of Cmd is pretty much entirely obviated by its base class.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
e39047e0dd jw.pkg.App: Derive from jw.pkg.lib.App

Derive jw.pkg.App from jw.pkg.lib.App. App.run() dissolves as follows:

- Its sub-command invocation logic is left to the base class - parser.add_arguments() are moved into self._add_arguments() - So is handling of early-parsed arguments - async def _run() is reimplemented to set some member variables

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-26 17:58:23 +01:00
c39c268d98 jw-projects.sh: Fix -p / --prefix help message

--prefix doesn't denote an "App Path Prefix", "Parent directory of project source directories" decribes it better.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-01-20 15:04:48 +01:00
ced42938e1 projects.mk / jw-projects.py: Support tmpl_dir && tmpls-dir

For a project to supply templates, it needs to advertise their location. For this, the tmpl_dir make variable is added to projects.mk. If other-project wants to get hold of some-project's templates, it can do, e.g.:

TEMPLATES = $(wilcard $(call tmpl_dir,some-project)/*.tmpl)

To achieve this, support for the tmpls-dir command is added to jw-projects.py.

Signed-off-by: Jan Lindemann <jan@janware.com>
2025-12-23 11:07:37 +01:00