Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
autoremove: install, delete and upgrade are now autoremove aware
Baptiste Daroussin committed 1 month ago
commit 329d20541daad65296cdf0c90a5ba4e15a9c836d
parent 4ca226d
13 files changed +365 -10
modified docs/pkg-delete.8
@@ -30,11 +30,11 @@
.Fl a
.Pp
.Nm
-
.Op Cm --{no-scripts,force,dry-run,quiet,recursive,yes}
+
.Op Cm --{no-scripts,force,dry-run,quiet,recursive,yes,autoremove}
.Op Cm --{case-sensitive,glob,case-insensitive,regex}
.Ar pkg-name ...
.Nm
-
.Op Cm --{no-scripts,dry-run,quiet,yes}
+
.Op Cm --{no-scripts,dry-run,quiet,yes,autoremove}
.Cm --all
.Sh DESCRIPTION
.Nm
@@ -124,6 +124,16 @@ Treat the package names as regular expressions according to the
.Xr re_format 7 .
.It Fl y , Cm --yes
Assume yes when asked for confirmation before package deletion.
+
.It Cm --autoremove
+
After deleting the requested packages, automatically remove any orphaned
+
packages that were installed as dependencies and are no longer required.
+
This is equivalent to running
+
.Xr pkg-autoremove 8
+
after the deletion.
+
Can also be enabled permanently via the
+
.Cm AUTOREMOVE
+
option in
+
.Xr pkg.conf 5 .
.It Cm --script-no-exec
Do not execute the pre-deinstall and post-deinstall scripts but still log
the scripts in the package database for future reference.
modified docs/pkg-install.8
@@ -28,7 +28,7 @@
.Ar <pkg-origin|pkg-name|pkg-name-version> ...
.Pp
.Nm
-
.Op Cm --{automatic,force,no-scripts,ignore-missing,register-only}
+
.Op Cm --{automatic,force,no-scripts,ignore-missing,register-only,autoremove}
.Op Cm --{dry-run,fetch-only,quiet,recursive,no-repo-update,yes}
.Op Cm --repository Ar reponame
.Op Cm --{case-sensitive,glob,case-insensitive,regex}
@@ -187,6 +187,16 @@ Assume yes when asked for confirmation before package installation.
.It Fl l , Cm --local-only
Skip the remote repository and only install from local archives.
Suppress the automatic update of the repository catalogue.
+
.It Cm --autoremove
+
After installing the requested packages, automatically remove any orphaned
+
packages that were installed as dependencies and are no longer required.
+
This is equivalent to running
+
.Xr pkg-autoremove 8
+
after the installation.
+
Can also be enabled permanently via the
+
.Cm AUTOREMOVE
+
option in
+
.Xr pkg.conf 5 .
.It Cm --script-no-exec
Do not execute the pre-install and post-install scripts but still log
the scripts
modified docs/pkg-upgrade.8
@@ -28,7 +28,7 @@
.Op Ar <pkg-origin|pkg-name|pkg-name-version> ...
.Pp
.Nm
-
.Op Cm --{force,no-scripts,dry-run,fetch-only}
+
.Op Cm --{force,no-scripts,dry-run,fetch-only,autoremove}
.Op Cm --{quiet,no-repo-update,yes}
.Op Cm --repository Ar reponame
.Op Cm --{case-sensitive,glob,case-insensitive,regex}
@@ -184,6 +184,16 @@ Treat the package names as regular expressions according to the
.Xr re_format 7 .
.It Fl y , Cm --yes
Assume yes when asked for confirmation before package installation.
+
.It Cm --autoremove
+
After upgrading the requested packages, automatically remove any orphaned
+
packages that were installed as dependencies and are no longer required.
+
This is equivalent to running
+
.Xr pkg-autoremove 8
+
after the upgrade.
+
Can also be enabled permanently via the
+
.Cm AUTOREMOVE
+
option in
+
.Xr pkg.conf 5 .
.It Cm --script-no-exec
Do not execute the pre-install, post-install, pre-deinstall, or post-deinstall
scripts but still log the scripts in the package database for future reference.
modified docs/pkg.conf.5
@@ -102,6 +102,16 @@ after each non dry-run call to
or
.Xr pkg-upgrade 8 .
Default: NO.
+
.It Cm AUTOREMOVE: boolean
+
Automatically remove orphaned packages after each non dry-run call to
+
.Xr pkg-install 8 ,
+
.Xr pkg-delete 8
+
or
+
.Xr pkg-upgrade 8 .
+
This is equivalent to running
+
.Xr pkg-autoremove 8
+
after each operation.
+
Default: NO.
.It Cm DEFAULT_ALWAYS_YES: boolean
When this option is enabled
.Xr pkg 1
modified libpkg/pkg_config.c
@@ -345,6 +345,11 @@ static struct config_entry c[] = {
		"NO",
	},
	{
+
		PKG_BOOL,
+
		"AUTOREMOVE",
+
		"NO",
+
	},
+
	{
		PKG_STRING,
		"DOT_FILE",
		NULL,
modified src/delete.c
@@ -41,8 +41,8 @@
void
usage_delete(void)
{
-
	fprintf(stderr, "Usage: pkg delete [-DfnqRy] [-Cgix] <pkg-name> ...\n");
-
	fprintf(stderr, "       pkg delete [-Dnqy] -a\n\n");
+
	fprintf(stderr, "Usage: pkg delete [-DfnqRy] [--autoremove] [-Cgix] <pkg-name> ...\n");
+
	fprintf(stderr, "       pkg delete [-Dnqy] [--autoremove] -a\n\n");
	fprintf(stderr, "For more information see 'pkg help delete'.\n");
}

@@ -54,6 +54,7 @@ exec_delete(int argc, char **argv)
	match_t		 match = MATCH_EXACT;
	pkg_flags	 f = PKG_FLAG_NONE;
	bool		 recursive_flag = false, rc = false;
+
	bool		 autoremove_flag = false;
	int		 retcode = EXIT_FAILURE;
	int		 ch;
	int		 i;
@@ -61,9 +62,11 @@ exec_delete(int argc, char **argv)
	int		 locked_pkgs = 0;
	int		 nbactions = 0;
	int		 scriptnoexec = 0;
+
	int		 autoremove = 0;

	struct option longopts[] = {
		{ "all",			no_argument,	NULL,	'a' },
+
		{ "autoremove",			no_argument,	&autoremove,	1 },
		{ "case-sensitive",		no_argument,	NULL,	'C' },
		{ "no-scripts",			no_argument,	NULL,	'D' },
		{ "script-no-exec",		no_argument,	&scriptnoexec,	1 },
@@ -120,6 +123,8 @@ exec_delete(int argc, char **argv)
		case 0:
			if (scriptnoexec)
				f |= PKG_FLAG_NOEXEC;
+
			if (autoremove)
+
				autoremove_flag = true;
			break;
		default:
			usage_delete();
@@ -250,6 +255,7 @@ exec_delete(int argc, char **argv)
		printf("%s", messages->buf);
	}
	pkgdb_compact(db);
+
	pkgcli_autoremove(db, autoremove_flag);

	retcode = EXIT_SUCCESS;

modified src/install.c
@@ -47,7 +47,7 @@ void
usage_install(void)
{
	fprintf(stderr,
-
	    "Usage: pkg install [-AfInFMqRUy] [-r reponame] [-Cgix] <pkg-name> ...\n\n");
+
	    "Usage: pkg install [-AfInFMqRUy] [--autoremove] [-r reponame] [-Cgix] <pkg-name> ...\n\n");
	fprintf(stderr, "For more information see 'pkg help install'.\n");
}

@@ -65,14 +65,17 @@ exec_install(int argc, char **argv)
	int		 lock_type = PKGDB_LOCK_ADVISORY;
	int		 nbactions = 0;
	int		 scriptnoexec = 0;
+
	int		 autoremove = 0;
	bool		 rc = true;
	bool		 local_only = false;
+
	bool		 autoremove_flag = false;
	match_t		 match = MATCH_EXACT;
	pkg_flags	 f = PKG_FLAG_NONE | PKG_FLAG_PKG_VERSION_TEST;
	c_charv_t	reponames = vec_init();

	struct option longopts[] = {
		{ "automatic",		no_argument,		NULL,	'A' },
+
		{ "autoremove",		no_argument,		&autoremove,	1 },
		{ "case-sensitive",	no_argument,		NULL,	'C' },
		{ "force",		no_argument,		NULL,	'f' },
		{ "fetch-only",		no_argument,		NULL,	'F' },
@@ -160,6 +163,8 @@ exec_install(int argc, char **argv)
		case 0:
			if (scriptnoexec == 1)
				f |= PKG_FLAG_NOEXEC;
+
			if (autoremove)
+
				autoremove_flag = true;
			break;
		default:
			usage_install();
@@ -277,6 +282,9 @@ exec_install(int argc, char **argv)
	if (!rc)
		status = EXIT_FAILURE;

+
	if (done && status == EXIT_SUCCESS)
+
		pkgcli_autoremove(db, autoremove_flag);
+

cleanup:
	pkgdb_release_lock(db, lock_type);
	pkg_jobs_free(jobs);
modified src/pkgcli.h
@@ -265,6 +265,7 @@ void job_status_end(xstring *);
int event_callback(void *data, struct pkg_event *ev);
int print_pkg(struct pkg *p, void *ctx);
void print_repository(struct pkg_repo *repo, bool pad);
+
void pkgcli_autoremove(struct pkgdb *db, bool flag);
void progressbar_start(const char *pmsg);
void progressbar_tick(int64_t current, int64_t total);
void progressbar_stop(void);
modified src/upgrade.c
@@ -53,7 +53,7 @@ static const char vuln_end_lit[] = "**END**";
void
usage_upgrade(void)
{
-
	fprintf(stderr, "Usage: pkg upgrade [-fInFqUy] [-r reponame] [-Cgix] <pkg-name> ...\n\n");
+
	fprintf(stderr, "Usage: pkg upgrade [-fInFqUy] [--autoremove] [-r reponame] [-Cgix] <pkg-name> ...\n\n");
	fprintf(stderr, "For more information see 'pkg help upgrade'.\n");
}

@@ -244,11 +244,14 @@ exec_upgrade(int argc, char **argv)
	int		 done = 0;
	int		 nbactions = 0;
	int		 scriptnoexec = 0;
+
	int		 autoremove = 0;
	bool	rc = true;
+
	bool	autoremove_flag = false;
	pkg_flags	 f = PKG_FLAG_NONE | PKG_FLAG_PKG_VERSION_TEST;
	c_charv_t	reponames = vec_init();

	struct option longopts[] = {
+
		{ "autoremove",		no_argument,		&autoremove,	1 },
		{ "case-sensitive",	no_argument,		NULL,	'C' },
		{ "force",		no_argument,		NULL,	'f' },
		{ "fetch-only",		no_argument,		NULL,	'F' },
@@ -313,6 +316,8 @@ exec_upgrade(int argc, char **argv)
		case 0:
			if (scriptnoexec == 1)
				f |= PKG_FLAG_NOEXEC;
+
			if (autoremove)
+
				autoremove_flag = true;
			break;
		default:
			usage_upgrade();
@@ -425,6 +430,9 @@ exec_upgrade(int argc, char **argv)
	else
		retcode = EXIT_FAILURE;

+
	if (done && retcode == EXIT_SUCCESS)
+
		pkgcli_autoremove(db, autoremove_flag);
+

cleanup:
	pkg_jobs_free(jobs);
	pkgdb_release_lock(db, lock_type);
modified src/utils.c
@@ -1209,3 +1209,45 @@ print_repository(struct pkg_repo *repo, bool pad)
				"ip_version", pkg_repo_ip_version(repo));
	printf("\n  }\n");
}
+

+
void
+
pkgcli_autoremove(struct pkgdb *db, bool flag)
+
{
+
	struct pkg_jobs *jobs = NULL;
+
	int nbactions;
+
	pkg_flags f = PKG_FLAG_FORCE;
+

+
	if (!flag && !pkg_object_bool(pkg_config_get("AUTOREMOVE")))
+
		return;
+
	if (dry_run)
+
		return;
+

+
	if (pkg_jobs_new(&jobs, PKG_JOBS_AUTOREMOVE, db) != EPKG_OK)
+
		return;
+

+
	pkg_jobs_set_flags(jobs, f);
+

+
	if (pkg_jobs_solve(jobs) != EPKG_OK) {
+
		pkg_jobs_free(jobs);
+
		return;
+
	}
+

+
	if ((nbactions = pkg_jobs_count(jobs)) == 0) {
+
		pkg_jobs_free(jobs);
+
		return;
+
	}
+

+
	if (!quiet) {
+
		print_jobs_summary(jobs,
+
		    "Autoremoval has been requested for the following "
+
		    "%d packages:\n\n", nbactions);
+
	}
+

+
	if (yes || query_yesno(false,
+
	    "\nProceed with autoremoval of packages? ")) {
+
		pkg_jobs_apply(jobs);
+
	}
+

+
	pkg_jobs_free(jobs);
+
	pkgdb_compact(db);
+
}
modified tests/frontend/delete.sh
@@ -20,7 +20,9 @@ tests_init \
	delete_no_scripts \
	delete_all_preserves_pkg \
	delete_recursive_force \
-
	delete_dry_run_no_remove
+
	delete_dry_run_no_remove \
+
	delete_autoremove \
+
	delete_autoremove_flag

delete_all_body() {
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "foo" "foo" "1"
@@ -460,3 +462,72 @@ EOF
	atf_check -s exit:0 pkg info -e pkgB
	atf_check -s exit:1 pkg info -e pkgC
}
+

+
delete_autoremove_body() {
+
	# test: automatic dependency; master: manual package depending on test
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "dep" "dep" "1"
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "master" "master" "1"
+
	cat << EOF >> master.ucl
+
deps: {
+
	dep {
+
		origin: dep,
+
		version: 1
+
	}
+
}
+
EOF
+

+
	atf_check -o ignore pkg register -A -M dep.ucl
+
	atf_check -o ignore pkg register -M master.ucl
+

+
	# Without AUTOREMOVE, delete master should leave dep
+
	atf_check \
+
		-o match:"Deinstalling master" \
+
		-o not-match:"dep" \
+
		-s exit:0 \
+
		pkg delete -yf master
+

+
	atf_check -s exit:0 pkg info -e dep
+

+
	# Reinstall
+
	atf_check -o ignore pkg register -M master.ucl
+

+
	# With AUTOREMOVE=YES, delete master should also autoremove dep
+
	echo "AUTOREMOVE=YES" > pkg.conf
+
	atf_check \
+
		-o match:"Deinstalling master" \
+
		-o match:"Deinstalling dep" \
+
		-s exit:0 \
+
		pkg -C ${TMPDIR}/pkg.conf delete -yf master
+

+
	# Both should be gone
+
	atf_check -s exit:1 pkg info -e master
+
	atf_check -s exit:1 pkg info -e dep
+
}
+

+
delete_autoremove_flag_body() {
+
	# Same as delete_autoremove but using --autoremove flag instead of config
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "dep" "dep" "1"
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "master" "master" "1"
+
	cat << EOF >> master.ucl
+
deps: {
+
	dep {
+
		origin: dep,
+
		version: 1
+
	}
+
}
+
EOF
+

+
	atf_check -o ignore pkg register -A -M dep.ucl
+
	atf_check -o ignore pkg register -M master.ucl
+

+
	# With --autoremove flag, delete master should also autoremove dep
+
	atf_check \
+
		-o match:"Deinstalling master" \
+
		-o match:"Deinstalling dep" \
+
		-s exit:0 \
+
		pkg delete -yf --autoremove master
+

+
	# Both should be gone
+
	atf_check -s exit:1 pkg info -e master
+
	atf_check -s exit:1 pkg info -e dep
+
}
modified tests/frontend/install.sh
@@ -8,7 +8,9 @@ tests_init \
	pre_script_fail \
	post_script_ignored \
	install_missing_dep \
-
	install_register_only
+
	install_register_only \
+
	install_autoremove \
+
	install_autoremove_flag

test_setup()
{
@@ -259,3 +261,88 @@ EOF
		-s exit:1 \
		test -d dir
}
+

+
install_autoremove_body() {
+
	# Pre-register: olddep (automatic), master depends on olddep
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "olddep" "olddep" "1"
+
	atf_check -o ignore pkg register -A -M olddep.ucl
+

+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "master" "master" "1"
+
	cat << EOF >> master.ucl
+
deps: {
+
	olddep {
+
		origin: olddep,
+
		version: 1
+
	}
+
}
+
EOF
+
	atf_check -o ignore pkg register -M master.ucl
+

+
	# Create master v2 in repo (no longer depends on olddep)
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "master2" "master" "2"
+
	atf_check pkg create -M master2.ucl -o ./repo
+

+
	cat << EOF > pkg.conf
+
PKG_DBDIR=${TMPDIR}
+
REPOS_DIR=[]
+
AUTOREMOVE=YES
+
repositories: {
+
	local: { url : file://${TMPDIR}/repo }
+
}
+
EOF
+

+
	atf_check -o ignore pkg -C ./pkg.conf repo ./repo
+
	atf_check -o ignore pkg -C ./pkg.conf update -f
+

+
	# Install/upgrade master: olddep should be autoremoved
+
	atf_check \
+
		-o match:"Upgrading master" \
+
		-o match:"Deinstalling olddep" \
+
		-s exit:0 \
+
		pkg -C ./pkg.conf install -y master
+

+
	atf_check -s exit:0 pkg info -e master
+
	atf_check -s exit:1 pkg info -e olddep
+
}
+

+
install_autoremove_flag_body() {
+
	# Same scenario but with --autoremove flag
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "olddep" "olddep" "1"
+
	atf_check -o ignore pkg register -A -M olddep.ucl
+

+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "master" "master" "1"
+
	cat << EOF >> master.ucl
+
deps: {
+
	olddep {
+
		origin: olddep,
+
		version: 1
+
	}
+
}
+
EOF
+
	atf_check -o ignore pkg register -M master.ucl
+

+
	# Create master v2 in repo (no longer depends on olddep)
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "master2" "master" "2"
+
	atf_check pkg create -M master2.ucl -o ./repo
+

+
	cat << EOF > pkg.conf
+
PKG_DBDIR=${TMPDIR}
+
REPOS_DIR=[]
+
repositories: {
+
	local: { url : file://${TMPDIR}/repo }
+
}
+
EOF
+

+
	atf_check -o ignore pkg -C ./pkg.conf repo ./repo
+
	atf_check -o ignore pkg -C ./pkg.conf update -f
+

+
	# Install/upgrade master with --autoremove: olddep should be removed
+
	atf_check \
+
		-o match:"Upgrading master" \
+
		-o match:"Deinstalling olddep" \
+
		-s exit:0 \
+
		pkg -C ./pkg.conf install -y --autoremove master
+

+
	atf_check -s exit:0 pkg info -e master
+
	atf_check -s exit:1 pkg info -e olddep
+
}
modified tests/frontend/upgrade.sh
@@ -17,6 +17,8 @@ tests_init \
	upgrade_glob_abi_os \
	upgrade_glob_abi_version \
	upgrade_glob_abi_arch \
+
	upgrade_autoremove \
+
	upgrade_autoremove_flag \

issue1881_body() {
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg pkg1 pkg_a 1
@@ -644,3 +646,88 @@ EOF
		-e ignore \
		pkg -o IGNORE_OSVERSION=yes -o ABI=${OS}:16:amd64 -o OSVERSION=1600000 -C ./pkg.conf install -qy testb
}
+

+
upgrade_autoremove_body() {
+
	# Install master v1 with olddep as automatic dependency
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "olddep" "olddep" "1.0"
+
	atf_check -o ignore pkg register -A -M olddep.ucl
+

+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "master" "master" "1.0"
+
	cat << EOF >> master.ucl
+
deps: {
+
	olddep {
+
		origin: olddep,
+
		version: "1.0"
+
	}
+
}
+
EOF
+
	atf_check -o ignore pkg register -M master.ucl
+

+
	# Create master v2 in repo (no longer depends on olddep)
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "master2" "master" "2.0"
+
	atf_check pkg create -M master2.ucl -o ./repo
+

+
	cat << EOF > pkg.conf
+
PKG_DBDIR=${TMPDIR}
+
REPOS_DIR=[]
+
AUTOREMOVE=YES
+
repositories: {
+
	local: { url : file://${TMPDIR}/repo }
+
}
+
EOF
+

+
	atf_check -o ignore pkg -C ./pkg.conf repo ./repo
+
	atf_check -o ignore pkg -C ./pkg.conf update -f
+

+
	# Upgrade: master 1.0 -> 2.0, olddep should be autoremoved
+
	atf_check \
+
		-o match:"Upgrading master" \
+
		-o match:"Deinstalling olddep" \
+
		-s exit:0 \
+
		pkg -C ./pkg.conf upgrade -y
+

+
	atf_check -s exit:0 pkg info -e master
+
	atf_check -s exit:1 pkg info -e olddep
+
}
+

+
upgrade_autoremove_flag_body() {
+
	# Same but with --autoremove flag instead of config
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "olddep" "olddep" "1.0"
+
	atf_check -o ignore pkg register -A -M olddep.ucl
+

+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "master" "master" "1.0"
+
	cat << EOF >> master.ucl
+
deps: {
+
	olddep {
+
		origin: olddep,
+
		version: "1.0"
+
	}
+
}
+
EOF
+
	atf_check -o ignore pkg register -M master.ucl
+

+
	# Create master v2 in repo (no longer depends on olddep)
+
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "master2" "master" "2.0"
+
	atf_check pkg create -M master2.ucl -o ./repo
+

+
	cat << EOF > pkg.conf
+
PKG_DBDIR=${TMPDIR}
+
REPOS_DIR=[]
+
repositories: {
+
	local: { url : file://${TMPDIR}/repo }
+
}
+
EOF
+

+
	atf_check -o ignore pkg -C ./pkg.conf repo ./repo
+
	atf_check -o ignore pkg -C ./pkg.conf update -f
+

+
	# Upgrade with --autoremove: olddep should be removed
+
	atf_check \
+
		-o match:"Upgrading master" \
+
		-o match:"Deinstalling olddep" \
+
		-s exit:0 \
+
		pkg -C ./pkg.conf upgrade -y --autoremove
+

+
	atf_check -s exit:0 pkg info -e master
+
	atf_check -s exit:1 pkg info -e olddep
+
}