Opened 12 months ago

Last modified 6 months ago

#18670 new enhancement

[pkgman] Let pkgman list leaf packages

Reported by: haikupr Owned by: nobody
Priority: normal Milestone: Unscheduled
Component: Applications/Command Line Tools Version: R1/Development
Keywords: Cc:
Blocked By: #15567 Blocking:
Platform: All

Description

I was trying to shrink the number of installed packages in order to quicken my Haiku's boot time. In order to do so I searched for a canned way to list all the installed leaf packages, ie. the ones which were once installed as a dependency be are now useless to the set of installed packages, and those I could remove safely. I found no easy way to obtain that list. Would it be possible to expand pkgman to be able to only list leaf package? Command line usage could look like pkgman search --leaf or something similar.

Change History (14)

comment:1 by waddlesplash, 12 months ago

Component: Sys-AdminApplications/Command Line Tools
Keywords: pkgman leaf removed
Owner: changed from haiku-web to nobody
Summary: Let pkgman list leaf packages[pkgman] Let pkgman list leaf packages

I thought we had a ticket similar to this already, but now I can't find it.

comment:2 by humdinger, 12 months ago

This one: #15567 ?

comment:3 by waddlesplash, 12 months ago

Blocked By: 15567 added

Indeed so.

comment:4 by madmax, 12 months ago

Not exactly what you want, but until the function is implemented this script can give you a subset of the packages that no other package depends on. Notice though it doesn't know whether they came as a dependency or you installed them yourself.

comment:5 by bipolar, 12 months ago

Somewhat similar to madmax's script, there's my `pkgman search --not-required` proof of concept :-)

Some months ago, I implemented that search --not-required in pkgman proper in two ways:

  • The first one following that naive/quick-and-dirty implementation (slightly inaccurate, pretty fast!).
  • the second, more accurate (takes versions into account), but scales poorly with the number of packages.

When used to only list *installed* packages that are not required, both take < 0.25s (having 500+ packages installed), but when doing it for ALL packages, the second version takes around 15 seconds, IIRC (on my Phenom II VM).

I should probably give it another go, and at least submit the second for review, in hopes there's anything salvageable.

Maybe something better can be written, and added to PackageKit's LibsolvSolver::FindPackages(), for example? (task for better programmers than I).

Last edited 12 months ago by bipolar (previous) (diff)

comment:6 by waddlesplash, 6 months ago

I don't think something could be added to FindPackages, but what about using a BSolverPackageSpecifierList, adding all the requirements to that, using the Solver to get back a BObjectList<BSolverPackage> of all the packages required, and then excluding all the packages that were in that set?

comment:7 by waddlesplash, 6 months ago

I played around with this for a while but couldn't manage to get anything to work. Ultimately what I came up with was:

static void
filter_required_packages(const BPackageManager& packageManager,
	BObjectList<BSolverPackage>& packages)
{
	std::set<BSolverPackage*> packagesSet;
	BSolverPackageSpecifierList requirements;
	for (int32 i = 0; i < packages.CountItems(); i++) {
		BSolverPackage* package = packages.ItemAt(i);
		if (package->Name().EndsWith("_source") || package->Name().EndsWith("_debuginfo"))
			continue;

		packagesSet.insert(package);

		BObjectList<BPackageResolvableExpression> requiresList
			= package->Info().RequiresList();
		for (int32 j = 0; j < requiresList.CountItems(); j++)
			requirements.AppendSpecifier(requiresList.ItemAt(j)->Name());
	}
	packages.MakeEmpty();

	BObjectList<BSolverPackage> requiredPackages;
	const BSolverPackageSpecifier* unmatched;
	status_t status = packageManager.Solver()->FindPackages(requirements,
		BSolver::B_FIND_INSTALLED_ONLY, requiredPackages, &unmatched);
	if (status != B_OK) {
		fprintf(stderr, "Failed to filter required packages: %s\n", strerror(status));
		if (unmatched != NULL)
			fprintf(stderr, "\t(unmatched: %s)\n", unmatched->SelectString().String());
		return;
	}

	for (int32 i = 0; i < requiredPackages.CountItems(); i++)
		packagesSet.erase(requiredPackages.ItemAt(i));

	std::set<BSolverPackage*>::const_iterator setIterator = packagesSet.begin();
	for (; setIterator != packagesSet.end(); setIterator++)
		packages.AddItem(*setIterator);
}

But this doesn't work because FindPackages just bails immediately claiming it can't find a package matching "haiku". (It's worth noting this uses a different codepath than the regular FindPackages and a different search mechanism; I don't know what else in the code even uses that mechanism, so I don't know whether it works or not.) Additionally it doesn't look very efficient anyway; it just iterates over the whole requirement list and adds packages that match each requirement one by one, rather than all-at-once.

comment:8 by bipolar, 6 months ago

Mmm, thanks for the pointer! Whatever I can use to transform my three nested for loops into something with a lower O() would help a lot!

PackageFS code gives me a headache each time I look at it, but... I will try to give it another go, and see if I can find the right classes/methods to call :-)

Thanks again.

Version 0, edited 6 months ago by bipolar (next)

comment:9 by waddlesplash, 6 months ago

I really think there should be some way to do this with libsolv, I just am not familiar enough to know what it is. There are a bunch of ways your code could be reworked to a lower O(), I just don't know if that's the route we should take...

in reply to:  9 comment:10 by bipolar, 6 months ago

Replying to waddlesplash:

I just don't know if that's the route we should take...

Indeed. I was just brute-forcing it (due to my limited skillset), thus why I didn't even send it for review, and why I hoped that something better could be added to libsolv itself (assuming it wasn't there already, and I just missed it).

Beats not having that info available, if you need it, but... I would also hope for having a better way of getting it :-)

in reply to:  7 comment:11 by madmax, 6 months ago

Replying to waddlesplash:

But this doesn't work because FindPackages just bails immediately claiming it can't find a package matching "haiku".

For whatever reason it does work passing flag = 0. And you'll also want ToString() instead of Name() for the requires expression so that it also uses the version and outputs old not required versions of icu, ffmpeg and that kind of packages.

in reply to:  9 comment:12 by madmax, 6 months ago

Replying to waddlesplash:

I really think there should be some way to do this with libsolv, I just am not familiar enough to know what it is.

Not familiar either, but browsing SUSE's zypper (and libzypp), I see they use solver_get_unneeded.

I also came across SOLVER_CLEANDEPS flag, which might be useful to automatically remove dependencies brought by a package when uninstalling it.

comment:13 by waddlesplash, 6 months ago

For whatever reason it does work passing flag = 0.

Interesting.

And you'll also want ToString() instead of Name()

I initially did use ToString() but got the same error, so I thought maybe that mode didn't support version restrictions or something.

which might be useful to automatically remove dependencies brought by a package when uninstalling it.

Ultimately we should just store/mark all user-installed packages so "autoremove" and CLEANDEPS can work properly.

comment:14 by waddlesplash, 6 months ago

On a related note, I think we should move to using a vendored libsolv in-tree rather than the package. Something so critical to package management infrastructure should be managed along with the OS, not as a separate package. We haven't upgraded it for a decade in part because of fear that an upgrade would break something and wouldn't be easily fixed.

Note: See TracTickets for help on using tickets.