diff --git a/ALTERNATIVES b/ALTERNATIVES
new file mode 100644
index 00000000..89d666f8
--- /dev/null
+++ b/ALTERNATIVES
@@ -0,0 +1,246 @@
+The XBPS alternatives framework
+-------------------------------
+
+-------------------------------------------------
+Package metadata stored in packages (props.plist)
+-------------------------------------------------
+
+The 'nvi' pkg declares an alternative group named 'vi':
+
+...
+alternatives
+
+ vi
+
+ /usr/bin/ex:/usr/bin/nvi
+ /usr/bin/vi:/usr/bin/nvi
+ /usr/share/man/man1/ex.1:/usr/share/man/man1/nvi.1
+ /usr/share/man/man1/vi.1:/usr/share/man/man1/nvi.1
+
+ ...
+
+...
+
+The strings need to follow the : convention, delimited by
+the ':' character.
+
+--------------------------------------------------------------------
+Package metadata stored in metadir (metadir/alternatives-0.48.plist)
+--------------------------------------------------------------------
+
+/var/db/xbps/alternatives-0.48.plist (dict):
+
+3 packages can provide the 'vi' alternative group: ex-vi, nvi or vim.
+The entry order determines the priority (first entry always wins).
+
+After running `xbps-install -Sy nvi ex-vi vim' the order is the following:
+
+...
+ vi
+
+ nvi
+ ex-vi
+ vim
+
+ ...
+...
+
+If the 'nvi' pkg is removed, and there's no alternative set for `vi',
+the 'ex-vi' pkg now becomes the default alternative:
+
+...
+ vi
+
+ ex-vi
+ vim
+
+ ...
+...
+
+The user now decides that 'vi' should be provided by the 'vim' pkg,
+so that the matching entry is put into the head:
+
+...
+ vi
+
+ vim
+ ex-vi
+
+ ...
+...
+
+When no packages provide an alternative group, the alternative group and its associated symlinks
+will be completely removed.
+
+
+--------------------
+xbps-alternatives(1)
+--------------------
+
+- Listing all available alternatives and current state:
+
+ $ xbps-alternatives -l
+ libGL
+ - libGL (current)
+ - /usr/lib/libGL.so -> /usr/lib/libGL-mesa.so
+ - /usr/lib/libGL.so.1 -> /usr/lib/libGL-mesa.so.1
+ - /usr/lib/xorg/modules/extensions/libglx.so -> /usr/lib/xorg/modules/extensions/libglx-xorg.so
+ - nvidia-libs
+ - /usr/lib/libGL.so -> /usr/lib/libGL-nvidia.so.1
+ - /usr/lib/libGL.so.1 -> /usr/lib/libGL-nvidia.so.1
+ - /usr/lib/xorg/modules/extensions/libglx.so -> /usr/lib/xorg/modules/extensions/libglx-nvidia.so
+ - catalyst-libs
+ - /usr/lib/libGL.so -> /usr/lib/libGL-fglrx.so.1
+ - /usr/lib/libGL.so.1 -> /usr/lib/libGL-fglrx.so.1
+ - /usr/lib/xorg/modules/extensions/libglx.so -> /usr/lib/xorg/modules/extensions/libglx-fglrx.so
+ ntpd
+ - openntpd (current)
+ - /etc/sv/ntpd -> /etc/sv/openntpd
+ - /usr/bin/ntpd -> /usr/bin/openntpd
+ - /usr/share/man/man1/ntpd.1 -> /usr/share/man/man1/openntpd.1
+ - ntp
+ - /etc/sv/ntpd -> /etc/sv/isc-ntpd
+ - /usr/bin/ntpd -> /usr/bin/isc-ntpd
+ - /usr/share/man/man1/ntpd.1 -> /usr/share/man/man1/isc-ntpd.1
+ - busybox
+ - /etc/sv/ntpd -> /etc/sv/busybox-ntpd
+ - /usr/bin/ntpd -> /usr/bin/busybox
+ sort
+ - coreutils (current)
+ - /usr/bin/sort -> /usr/bin/coreutils-sort
+ - /usr/share/man/man1/sort.1 -> /usr/share/man/man1/coreutils-sort.1
+ - busybox
+ - /usr/bin/sort -> /usr/bin/busybox
+ vi
+ - nvi (current)
+ - /usr/bin/ex -> /usr/bin/nvi
+ - /usr/bin/vi -> /usr/bin/nvi
+ - /usr/share/man/man1/ex.1 -> /usr/share/man/man1/nvi.1
+ - /usr/share/man/man1/vi.1 -> /usr/share/man/man1/nvi.1
+ - vim
+ - /usr/bin/ex -> /usr/bin/vim-ex
+ - /usr/bin/vi -> /usr/bin/vim
+ - /usr/share/man/man1/ex.1 -> /usr/share/man/man1/vim-ex.1
+ - /usr/share/man/man1/vi.1 -> /usr/share/man/man1/vim.1
+
+ ...
+
+- Listing available alternative groups of a package:
+
+ $ xbps-alternatives -l busybox
+ ntpd
+ - /etc/sv/ntpd -> /etc/sv/busybox-ntpd
+ - /usr/bin/ntpd -> /usr/bin/busybox
+ sort
+ - /usr/bin/sort -> /usr/bin/busybox
+ ...
+
+- Apply all alternative groups specified by the 'nvi' package (declares one group: vi):
+
+ $ xbps-alternatives -s nvi
+ Switched 'vi' alternatives group to 'nvi'.
+ Creating 'vi' alternatives group symlink: /usr/bin/ex -> /usr/bin/nvi
+ Creating 'vi' alternatives group symlink: /usr/bin/vi -> /usr/bin/nvi
+ Creating 'vi' alternatives group symlink: /usr/share/man/man1/ex.1 -> /usr/share/man/man1/nvi.1
+ Creating 'vi' alternatives group symlink: /usr/share/man/man1/vi.1 -> /usr/share/man/man1/nvi.1
+ $
+
+- Creating a specific alternative group of a package containing multiple groups:
+
+ $ xbps-alternatives -s busybox -g ntpd
+ Switched 'ntpd' alternatives group to 'busybox'.
+ Creating 'ntpd' alternatives group symlink: /etc/sv/ntpd -> /etc/sv/busybox-ntpd
+ Creating 'ntpd' alternatives group symlink: /usr/bin/ntpd -> /usr/bin/busybox
+ $
+
+---------------
+xbps-install(1)
+---------------
+
+Installing a package with an alternatives group for the first time:
+
+ $ xbps-install -Syv vim
+ Name Action Version New version Download size
+ vim install - 1.0_1 -
+
+ Free space on disk: 49GB
+
+ [*] Downloading binary packages
+
+ [*] Verifying package integrity
+ vim-1.0_1: verifying SHA256 hash...
+
+ [*] Running transaction tasks
+ vim-1.0_1: unpacking ...
+ vim-1.0_1: unpacked file `./usr/bin/vim' (0 bytes)
+ vim-1.0_1: unpacked file `./usr/share/man/man1/vim.1' (0 bytes)
+ vim-1.0_1: registered 'vi' alternatives group
+ Creating 'vi' alternatives group symlink: /usr/bin/vi -> /usr/bin/vim
+ Creating 'vi' alternatives group symlink: /usr/bin/ex -> /usr/bin/vim
+ Creating 'vi' alternatives group symlink: /usr/bin/view -> /usr/bin/vim
+ Creating 'vi' alternatives group symlink: /usr/share/man/man1/ex.1 -> /usr/share/man/man1/vim.1
+ Creating 'vi' alternatives group symlink: /usr/share/man/man1/vi.1 -> /usr/share/man/man1/vim.1
+ Creating 'vi' alternatives group symlink: /usr/share/man/man1/view.1 -> /usr/share/man/man1/vim.1
+
+ [*] Configuring unpacked packages
+ vim-1.0_1: configuring ...
+ vim-1.0_1: installed successfully.
+
+ 0 downloaded, 1 installed, 0 updated, 1 configured, 0 removed.
+ $
+
+Installing a package that provides a 'vi' alternatives group (nvi):
+
+ $ xbps-install -Syv nvi
+ Name Action Version New version Download size
+ nvi install - 1.0_1 -
+
+ Free space on disk: 49GB
+
+ [*] Downloading binary packages
+
+ [*] Verifying package integrity
+ nvi-1.0_1: verifying SHA256 hash...
+
+ [*] Running transaction tasks
+ nvi-1.0_1: unpacking ...
+ nvi-1.0_1: unpacked file `./usr/bin/nvi' (0 bytes)
+ nvi-1.0_1: unpacked file `./usr/share/man/man1/nvi.1' (0 bytes)
+ nvi-1.0_1: registered 'vi' alternatives group
+
+ [*] Configuring unpacked packages
+ nvi-1.0_1: configuring ...
+ nvi-1.0_1: installed successfully.
+
+ 0 downloaded, 1 installed, 0 updated, 1 configured, 0 removed.
+ $
+
+Removing a package that was the default provider of the 'vi' alternatives group (vim):
+
+ $ xbps-remove -yv vim
+ Name Action Version New version Download size
+ vim remove 1.0_1 - -
+
+ Free space on disk: 49GB
+
+ Removing `vim-1.0_1' ...
+ Removing 'vi' alternatives group symlink: /usr/bin/vi
+ Removing 'vi' alternatives group symlink: /usr/bin/ex
+ Removing 'vi' alternatives group symlink: /usr/bin/view
+ Removing 'vi' alternatives group symlink: /usr/share/man/man1/ex.1
+ Removing 'vi' alternatives group symlink: /usr/share/man/man1/vi.1
+ Removing 'vi' alternatives group symlink: /usr/share/man/man1/view.1
+ vim-1.0_1: unregistered 'vi' alternatives group
+ Switched 'vi' alternatives group to 'nvi'
+ Creating 'vi' alternatives group symlink: /usr/bin/vi -> /usr/bin/nvi
+ Creating 'vi' alternatives group symlink: /usr/bin/ex -> /usr/bin/nvi
+ Creating 'vi' alternatives group symlink: /usr/bin/view -> /usr/bin/nvi
+ Creating 'vi' alternatives group symlink: /usr/share/man/man1/ex.1 -> /usr/share/man/man1/nvi.1
+ Creating 'vi' alternatives group symlink: /usr/share/man/man1/vi.1 -> /usr/share/man/man1/nvi.1
+ Creating 'vi' alternatives group symlink: /usr/share/man/man1/view.1 -> /usr/share/man/man1/nvi.1
+ Removed file `/usr/share/man/man1/vim.1'
+ Removed file `/usr/bin/vim'
+ Removed `vim-1.0_1' successfully.
+
+ 0 downloaded, 0 installed, 0 updated, 0 configured, 1 removed.
+ $
diff --git a/bin/xbps-create/main.c b/bin/xbps-create/main.c
index 5448b393..68b52566 100644
--- a/bin/xbps-create/main.c
+++ b/bin/xbps-create/main.c
@@ -102,6 +102,9 @@ usage(void)
" -s --desc Short description (max 80 characters).\n"
" -t --tags A list of tags/categories (blank separated list).\n"
" -V --version Prints XBPS release version.\n"
+ " --alternatives List of available alternatives this pkg provides.\n"
+ " This expects a blank separated list of ::, e.g\n"
+ " 'vi:/usr/bin/vi:/usr/bin/vim foo:/usr/bin/foo:/usr/bin/blah'.\n"
" --build-options A string with the used build options.\n"
" --compression Compression format: none, gzip, bzip2, xz (default).\n"
" --shlib-provides List of provided shared libraries (blank separated list,\n"
@@ -162,6 +165,72 @@ out:
xbps_object_release(array);
}
+static void
+process_one_alternative(const char *altgrname, const char *val)
+{
+ xbps_dictionary_t d;
+ xbps_array_t a;
+ char *altfiles;
+ bool alloc = false;
+
+ if ((d = xbps_dictionary_get(pkg_propsd, "alternatives")) == NULL) {
+ d = xbps_dictionary_create();
+ assert(d);
+ alloc = true;
+ }
+ if ((a = xbps_dictionary_get(d, altgrname)) == NULL) {
+ a = xbps_array_create();
+ assert(a);
+ }
+ altfiles = strchr(val, ':') + 1;
+ assert(altfiles);
+
+ xbps_array_add_cstring(a, altfiles);
+ xbps_dictionary_set(d, altgrname, a);
+ xbps_dictionary_set(pkg_propsd, "alternatives", d);
+
+ if (alloc) {
+ xbps_object_release(a);
+ xbps_object_release(d);
+ }
+}
+
+
+static void
+process_dict_of_arrays(const char *key, const char *val)
+{
+ char *altgrname, *args, *p, *saveptr;
+
+ assert(key);
+
+ if (val == NULL)
+ return;
+
+ args = strdup(val);
+ assert(args);
+
+ if (strchr(args, ' ') == NULL) {
+ altgrname = strtok(args, ":");
+ assert(altgrname);
+ process_one_alternative(altgrname, val);
+ goto out;
+ }
+
+ for ((p = strtok_r(args, " ", &saveptr)); p;
+ (p = strtok_r(NULL, " ", &saveptr))) {
+ char *b;
+
+ b = strdup(p);
+ assert(b);
+ altgrname = strtok(b, ":");
+ assert(altgrname);
+ process_one_alternative(altgrname, p);
+ free(b);
+ }
+out:
+ free(args);
+}
+
static void
process_file(const char *file, const char *key)
{
@@ -615,6 +684,7 @@ main(int argc, char **argv)
{ "shlib-requires", required_argument, NULL, '1' },
{ "build-options", required_argument, NULL, '2' },
{ "compression", required_argument, NULL, '3' },
+ { "alternatives", required_argument, NULL, '4' },
{ NULL, 0, NULL, 0 }
};
struct archive *ar;
@@ -624,7 +694,7 @@ main(int argc, char **argv)
const char *conflicts, *deps, *homepage, *license, *maint, *bwith;
const char *provides, *pkgver, *replaces, *reverts, *desc, *ldesc;
const char *arch, *config_files, *mutable_files, *version;
- const char *buildopts, *shlib_provides, *shlib_requires;
+ const char *buildopts, *shlib_provides, *shlib_requires, *alternatives;
const char *compression, *tags = NULL, *srcrevs = NULL;
char *pkgname, *binpkg, *tname, *p, cwd[PATH_MAX-1];
bool quiet = false, preserve = false;
@@ -634,7 +704,7 @@ main(int argc, char **argv)
arch = conflicts = deps = homepage = license = maint = compression = NULL;
provides = pkgver = replaces = reverts = desc = ldesc = bwith = NULL;
buildopts = config_files = mutable_files = shlib_provides = NULL;
- shlib_requires = NULL;
+ alternatives = shlib_requires = NULL;
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
if (optarg && strcmp(optarg, "") == 0)
@@ -716,6 +786,9 @@ main(int argc, char **argv)
case '3':
compression = optarg;
break;
+ case '4':
+ alternatives = optarg;
+ break;
case '?':
default:
usage();
@@ -798,6 +871,7 @@ main(int argc, char **argv)
process_array("reverts", reverts);
process_array("shlib-provides", shlib_provides);
process_array("shlib-requires", shlib_requires);
+ process_dict_of_arrays("alternatives", alternatives);
/* save cwd */
memset(&cwd, 0, sizeof(cwd));
diff --git a/include/xbps.h.in b/include/xbps.h.in
index 3cda9ddd..c632dfab 100644
--- a/include/xbps.h.in
+++ b/include/xbps.h.in
@@ -48,7 +48,7 @@
*
* This header documents the full API for the XBPS Library.
*/
-#define XBPS_API_VERSION "20150603"
+#define XBPS_API_VERSION "20151018"
#ifndef XBPS_VERSION
#define XBPS_VERSION "UNSET"
@@ -96,6 +96,12 @@
*/
#define XBPS_PKGDB "pkgdb-0.38.plist"
+/**
+ * @def XBPS_ALTERNATIVES
+ * Filename for the package alternatives database.
+ */
+#define XBPS_ALTERNATIVES "alternatives-0.48.plist"
+
/**
* @def XBPS_PKGPROPS
* Filename for package metadata property list.
@@ -280,6 +286,11 @@ extern "C" {
* - XBPS_STATE_INVALID_DEP: package has an invalid dependency.
* - XBPS_STATE_SHOW_INSTALL_MSG: package must show a post-install message.
* - XBPS_STATE_SHOW_REMOVE_MSG: package must show a pre-remove message.
+ * - XBPS_STATE_ALTGROUP_ADDED: package has registered an alternative group.
+ * - XBPS_STATE_ALTGROUP_REMOVED: package has unregistered an alternative group.
+ * - XBPS_STATE_ALTGROUP_SWITCHED: alternative group has been switched.
+ * - XBPS_STATE_ALTGROUP_LINK_ADDED: link added by an alternative group.
+ * - XBPS_STATE_ALTGROUP_LINK_REMOVED: link removed by an alternative group.
* - XBPS_STATE_UNPACK_FILE_PRESERVED: package unpack preserved a file.
* - XBPS_STATE_PKGDB: pkgdb upgrade in progress.
* - XBPS_STATE_PKGDB_DONE: pkgdb has been upgraded successfully.
@@ -328,7 +339,12 @@ typedef enum xbps_state {
XBPS_STATE_UNPACK_FILE_PRESERVED,
XBPS_STATE_PKGDB,
XBPS_STATE_PKGDB_DONE,
- XBPS_STATE_TRANS_ADDPKG
+ XBPS_STATE_TRANS_ADDPKG,
+ XBPS_STATE_ALTGROUP_ADDED,
+ XBPS_STATE_ALTGROUP_REMOVED,
+ XBPS_STATE_ALTGROUP_SWITCHED,
+ XBPS_STATE_ALTGROUP_LINK_ADDED,
+ XBPS_STATE_ALTGROUP_LINK_REMOVED
} xbps_state_t;
/**
@@ -514,6 +530,7 @@ struct xbps_handle {
*/
xbps_dictionary_t pkgdb_revdeps;
xbps_dictionary_t vpkgd;
+ xbps_dictionary_t alternatives;
/**
* @var pkgdb
*
@@ -629,6 +646,10 @@ void xbps_dbg_printf_append(struct xbps_handle *, const char *, ...);
void xbps_error_printf(const char *, ...);
void xbps_warn_printf(const char *, ...);
+int xbps_alternatives_flush(struct xbps_handle *xhp);
+int xbps_alternatives_register(struct xbps_handle *, xbps_dictionary_t);
+int xbps_alternatives_unregister(struct xbps_handle *, xbps_dictionary_t);
+
/**
* Initialize the XBPS library with the following steps:
*
diff --git a/lib/Makefile b/lib/Makefile
index 3ef2a237..571e621b 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -42,6 +42,7 @@ OBJS += plist.o plist_find.o plist_match.o archive.o
OBJS += plist_remove.o plist_fetch.o util.o util_hash.o
OBJS += repo.o repo_pkgdeps.o repo_sync.o
OBJS += rpool.o cb_util.o proplib_wrapper.o
+OBJS += package_alternatives.o
OBJS += $(EXTOBJS) $(COMPAT_SRCS)
.PHONY: all
diff --git a/lib/package_alternatives.c b/lib/package_alternatives.c
new file mode 100644
index 00000000..40858436
--- /dev/null
+++ b/lib/package_alternatives.c
@@ -0,0 +1,280 @@
+/*-
+ * Copyright (c) 2015 Juan Romero Pardines.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include "xbps_api_impl.h"
+
+/*
+ * Alternatives framework for xbps.
+ */
+static char *
+left(const char *str)
+{
+ char *p;
+ size_t len;
+
+ p = strdup(str);
+ len = strlen(p) - strlen(strchr(p, ':'));
+ p[len] = '\0';
+
+ return p;
+}
+
+static const char *
+right(const char *str)
+{
+ return strchr(str, ':') + 1;
+}
+
+#if 0
+static int
+make_symlink(const char *target, const char *link)
+{
+ char *t, *l, *tdir, *ldir;
+
+ tdir = strdup(target);
+ assert(tdir);
+ ldir = strdup(link);
+ assert(ldir);
+
+}
+#endif
+
+static void
+xbps_alternatives_init(struct xbps_handle *xhp)
+{
+ char *plist;
+
+ if (xbps_object_type(xhp->alternatives) == XBPS_TYPE_DICTIONARY)
+ return;
+
+ plist = xbps_xasprintf("%s/%s", xhp->metadir, XBPS_ALTERNATIVES);
+ xhp->alternatives = xbps_dictionary_internalize_from_file(plist);
+ free(plist);
+
+ if (xhp->alternatives == NULL)
+ xhp->alternatives = xbps_dictionary_create();
+}
+
+int
+xbps_alternatives_flush(struct xbps_handle *xhp)
+{
+ char *plist;
+
+ if (xbps_object_type(xhp->alternatives) != XBPS_TYPE_DICTIONARY)
+ return 0;
+
+ /* ... and then write dictionary to disk */
+ plist = xbps_xasprintf("%s/%s", xhp->metadir, XBPS_ALTERNATIVES);
+ if (!xbps_dictionary_externalize_to_file(xhp->alternatives, plist)) {
+ free(plist);
+ return EINVAL;
+ }
+ free(plist);
+
+ return 0;
+}
+
+static int
+remove_symlinks(struct xbps_handle *xhp, xbps_array_t a, const char *grname)
+{
+ unsigned int i, cnt;
+
+ cnt = xbps_array_count(a);
+ for (i = 0; i < cnt; i++) {
+ xbps_string_t str;
+ char *l, *lnk;
+
+ str = xbps_array_get(a, i);
+ l = left(xbps_string_cstring_nocopy(str));
+ assert(l);
+ lnk = xbps_xasprintf("%s%s", xhp->rootdir, l);
+ xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_LINK_REMOVED, 0, NULL,
+ "Removing '%s' alternatives group symlink: %s", grname, l);
+ unlink(lnk);
+ free(lnk);
+ free(l);
+ }
+
+ return 0;
+}
+
+static int
+create_symlinks(struct xbps_handle *xhp, xbps_array_t a, const char *grname)
+{
+ unsigned int i, cnt;
+
+ cnt = xbps_array_count(a);
+ for (i = 0; i < cnt; i++) {
+ xbps_string_t str;
+ char *l, *lnk;
+ const char *tgt;
+
+ str = xbps_array_get(a, i);
+ l = left(xbps_string_cstring_nocopy(str));
+ assert(l);
+ tgt = right(xbps_string_cstring_nocopy(str));
+ assert(tgt);
+ lnk = xbps_xasprintf("%s%s", xhp->rootdir, l);
+ xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_LINK_ADDED, 0, NULL,
+ "Creating '%s' alternatives group symlink: %s -> %s", grname, l, tgt);
+ unlink(lnk);
+ symlink(tgt, lnk);
+ free(lnk);
+ free(l);
+ }
+
+ return 0;
+}
+
+int
+xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
+{
+ xbps_array_t allkeys;
+ xbps_dictionary_t alternatives;
+ const char *pkgver;
+ char *pkgname;
+ int rv = 0;
+
+ alternatives = xbps_dictionary_get(pkgd, "alternatives");
+ if (!xbps_dictionary_count(alternatives))
+ return 0;
+
+ xbps_alternatives_init(xhp);
+
+ xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver);
+ if ((pkgname = xbps_pkg_name(pkgver)) == NULL)
+ return EINVAL;
+
+ allkeys = xbps_dictionary_all_keys(alternatives);
+ for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
+ xbps_array_t array;
+ xbps_object_t keysym;
+ const char *first = NULL, *keyname;
+
+ keysym = xbps_array_get(allkeys, i);
+ keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
+
+ array = xbps_dictionary_get(xhp->alternatives, keyname);
+ if (array == NULL)
+ continue;
+
+ xbps_array_get_cstring_nocopy(array, 0, &first);
+ if (strcmp(pkgname, first) == 0) {
+ /* this pkg is the current alternative for this group */
+ rv = remove_symlinks(xhp,
+ xbps_dictionary_get(alternatives, keyname),
+ keyname);
+ if (rv != 0)
+ break;
+ }
+ xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_REMOVED, 0, NULL,
+ "%s: unregistered '%s' alternatives group", pkgver, keyname);
+ xbps_remove_string_from_array(array, pkgname);
+ if (xbps_array_count(array) == 0) {
+ xbps_dictionary_remove(xhp->alternatives, keyname);
+ } else {
+ xbps_dictionary_t curpkgd;
+
+ first = NULL;
+ xbps_array_get_cstring_nocopy(array, 0, &first);
+ curpkgd = xbps_pkgdb_get_pkg(xhp, first);
+ assert(curpkgd);
+ xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_SWITCHED, 0, NULL,
+ "Switched '%s' alternatives group to '%s'", keyname, first);
+ alternatives = xbps_dictionary_get(curpkgd, "alternatives");
+ rv = create_symlinks(xhp,
+ xbps_dictionary_get(alternatives, keyname),
+ keyname);
+ if (rv != 0)
+ break;
+ }
+
+ }
+ xbps_object_release(allkeys);
+ free(pkgname);
+
+ return rv;
+}
+
+int
+xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
+{
+ xbps_array_t allkeys;
+ xbps_dictionary_t alternatives;
+ const char *pkgver;
+ char *pkgname;
+ int rv = 0;
+
+ alternatives = xbps_dictionary_get(pkgd, "alternatives");
+ if (!xbps_dictionary_count(alternatives))
+ return 0;
+
+ xbps_alternatives_init(xhp);
+
+ xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver);
+ pkgname = xbps_pkg_name(pkgver);
+ if (pkgname == NULL)
+ return EINVAL;
+
+ allkeys = xbps_dictionary_all_keys(alternatives);
+ for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
+ xbps_array_t array;
+ xbps_object_t keysym;
+ const char *keyname;
+ bool alloc = false;
+
+ keysym = xbps_array_get(allkeys, i);
+ keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
+
+ array = xbps_dictionary_get(xhp->alternatives, keyname);
+ if (array == NULL) {
+ alloc = true;
+ array = xbps_array_create();
+ }
+ xbps_array_add_cstring(array, pkgname);
+ xbps_dictionary_set(xhp->alternatives, keyname, array);
+ xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_ADDED, 0, NULL,
+ "%s: registered '%s' alternatives group", pkgver, keyname);
+ if (alloc) {
+ /* apply alternatives for this group */
+ rv = create_symlinks(xhp,
+ xbps_dictionary_get(alternatives, keyname),
+ keyname);
+ xbps_object_release(array);
+ if (rv != 0)
+ break;
+ }
+ }
+ xbps_object_release(allkeys);
+ free(pkgname);
+
+ return rv;
+}
diff --git a/lib/package_remove.c b/lib/package_remove.c
index a3d1741c..288fd3f8 100644
--- a/lib/package_remove.c
+++ b/lib/package_remove.c
@@ -318,6 +318,10 @@ xbps_remove_pkg(struct xbps_handle *xhp, const char *pkgver, bool update)
if ((rv = xbps_cb_message(xhp, pkgd, "remove-msg")) != 0)
goto out;
+ /* unregister alternatives */
+ if ((rv = xbps_alternatives_unregister(xhp, pkgd)) != 0)
+ goto out;
+
/*
* If updating a package, we just need to execute the current
* pre-remove action target and we are done. Its files will be
diff --git a/lib/package_unpack.c b/lib/package_unpack.c
index 94d7d97b..0fb96d7b 100644
--- a/lib/package_unpack.c
+++ b/lib/package_unpack.c
@@ -622,6 +622,14 @@ xbps_unpack_binary_pkg(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod)
"%s: [unpack] failed to set state to unpacked: %s",
pkgver, strerror(rv));
}
+ /* register alternatives */
+ if ((rv = xbps_alternatives_register(xhp, pkg_repod)) != 0) {
+ xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
+ rv, pkgver,
+ "%s: [unpack] failed to register alternatives: %s",
+ pkgver, strerror(rv));
+ }
+
out:
if (pkg_fd != -1)
close(pkg_fd);
diff --git a/lib/transaction_commit.c b/lib/transaction_commit.c
index b018e5d3..d50c7bd1 100644
--- a/lib/transaction_commit.c
+++ b/lib/transaction_commit.c
@@ -350,6 +350,10 @@ xbps_transaction_commit(struct xbps_handle *xhp)
goto out;
}
}
+ /* flush changes to the alternatives framework */
+ if ((rv = xbps_alternatives_flush(xhp)) != 0)
+ goto out;
+
/* if there are no packages to install or update we are done */
if (!xbps_dictionary_get(xhp->transd, "total-update-pkgs") &&
!xbps_dictionary_get(xhp->transd, "total-install-pkgs"))