changeset 28023:915b3630eed0

pkg.m: Don't unload dependency packages when their dependers are loaded (bug #57522). * pkg.m: Explain "-nodeps" option for "pkg load" and "pkg unload"; add one more example with dependency. * get_inverse_dependencies.m: New function. * scripts/pkg/module.mk: Add get_inverse_dependencies.m to build system. * unload_packages.m: rewrite parts of code; when checking input mention all requested but non-installed packages instead of just the first; track down loaded depender packages and if found, emit a sensible error message; process handle_deps argument ('-nodeps'). * describe.m: Also display installed packages that depend on requested package.
author Philip Nienhuis <prnienhuis@users.sf.net>
date Sat, 25 Jan 2020 15:26:07 +0100
parents 306df6825dd9
children c28b8ba841fb
files scripts/pkg/module.mk scripts/pkg/pkg.m scripts/pkg/private/describe.m scripts/pkg/private/get_inverse_dependencies.m scripts/pkg/private/unload_packages.m
diffstat 5 files changed, 141 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/pkg/module.mk	Tue Jan 28 07:59:52 2020 -0800
+++ b/scripts/pkg/module.mk	Sat Jan 25 15:26:07 2020 +0100
@@ -13,6 +13,7 @@
   %reldir%/private/get_description.m \
   %reldir%/private/get_forge_download.m \
   %reldir%/private/get_forge_pkg.m \
+  %reldir%/private/get_inverse_dependencies.m \
   %reldir%/private/get_unsatisfied_deps.m \
   %reldir%/private/getarch.m \
   %reldir%/private/getarchdir.m \
--- a/scripts/pkg/pkg.m	Tue Jan 28 07:59:52 2020 -0800
+++ b/scripts/pkg/pkg.m	Sat Jan 25 15:26:07 2020 +0100
@@ -133,9 +133,32 @@
 ## @noindent
 ## adds the @code{image} package to the path.
 ##
+## Note: When loading a package, @code{pkg} will automatically try to load
+## any unloaded dependencies as well, unless the @option{-nodeps} flag has
+## been specified.  For example,
+##
+## @example
+## pkg load signal
+## @end example
+##
+## @noindent
+## adds the @code{signal} package and also tries to load its dependency: the
+## @code{control} package.  Be aware that the functionality of package(s)
+## loaded will probably be impacted by use of the @option{-nodeps} flag.  Even
+## if necessary dependencies are loaded later, the functionality of depender
+## packages can still be affected because the optimal loading order of may
+## not have been followed.
+##
 ## @item unload
 ## Remove named packages from the path.  After unloading a package it is
-## no longer possible to use the functions provided by the package.
+## no longer possible to use the functions provided by the package.  Trying
+## to unload a package that other loaded packages still depend on will result
+## in an error; no packages will be unloaded in this case.  A package can
+## be forcibly removed with the @option{-nodeps} flag, but be aware that the
+## functionality of depender packages will likely be affected.  As when loading
+## packages, reloading dependencies after having unloaded them with the
+## @option{-nodeps} flag may not restore all functionality of the depender
+## packages as the required loading order may be incorrect.
 ##
 ## @item list
 ## Show the list of currently installed packages.  For example,
--- a/scripts/pkg/private/describe.m	Tue Jan 28 07:59:52 2020 -0800
+++ b/scripts/pkg/private/describe.m	Sat Jan 25 15:26:07 2020 +0100
@@ -31,8 +31,10 @@
 function [pkg_desc_list, flag] = describe (pkgnames, verbose, local_list, global_list)
 
   ## Get the list of installed packages.
-  installed_pkgs_lst = installed_packages(local_list, global_list);
+  installed_pkgs_lst = installed_packages (local_list, global_list);
   num_packages = length (installed_pkgs_lst);
+  ## Add inverse dependencies to "installed_pkgs_lst"
+  installed_pkgs_lst = get_inverse_dependencies (installed_pkgs_lst);
 
   if (isempty (pkgnames))
     describe_all = true;
@@ -64,6 +66,7 @@
       pkg_desc_list{name_pos}.description = installed_pkgs_lst{i}.description;
       pkg_desc_list{name_pos}.depends = installed_pkgs_lst{i}.depends;
       pkg_desc_list{name_pos}.provides = parse_pkg_idx (installed_pkgs_lst{i}.dir);
+      pkg_desc_list{name_pos}.invdeps = unique (installed_pkgs_lst{i}.invdeps);
 
     endif
   endfor
@@ -86,6 +89,7 @@
                                  pkg_desc_list{i}.provides,
                                  pkg_desc_list{i}.description,
                                  pkg_desc_list{i}.depends,
+                                 pkg_desc_list{i}.invdeps,
                                  flag{i}, verbose);
     endfor
   endif
@@ -148,7 +152,8 @@
 
 
 function print_package_description (pkg_name, pkg_ver, pkg_idx_struct,
-                                    pkg_desc, pkg_deps, status, verbose)
+                                    pkg_desc, pkg_deps, pkg_invd, status,
+                                    verbose)
 
   printf ("---\nPackage name:\n\t%s\n", pkg_name);
   printf ("Version:\n\t%s\n", pkg_ver);
@@ -157,6 +162,10 @@
                       "UniformOutput", false);
   pkg_deps = strjoin (pkg_deps, "\n\t");
   printf ("Depends on:\n\t%s\n", pkg_deps);
+
+  pkg_invd = strjoin (pkg_invd, "\n\t");
+  printf ("Depended on by:\n\t%s\n", pkg_invd);
+
   printf ("Status:\n\t%s\n", status);
   if (verbose)
     printf ("---\nProvides:\n");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/pkg/private/get_inverse_dependencies.m	Sat Jan 25 15:26:07 2020 +0100
@@ -0,0 +1,51 @@
+########################################################################
+##
+## Copyright (C) 2020 The Octave Project Developers
+##
+## See the file COPYRIGHT.md in the top-level directory of this
+## distribution or <https://octave.org/copyright/>.
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+##
+########################################################################
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{installed_pkgs_list} =} get_inverse_dependencies (@var{installed_pkgs_lst})
+## Find inverse dependencies, if any, for each package, and store in
+## the struct field "invdeps".
+##
+## @end deftypefn
+
+function installed_pkgs_lst = get_inverse_dependencies (installed_pkgs_lst)
+
+  for i = 1:numel (installed_pkgs_lst)
+    installed_pkgs_lst{i}.invdeps = {};  # initialize invdeps field
+  endfor
+
+  for i = 1:numel (installed_pkgs_lst)
+#    keyboard;
+    pdeps = installed_pkgs_lst{i}.depends;
+    for j = 1:numel (pdeps)
+      pdep_nm = pdeps{j}.package;
+      if (! strcmp (pdep_nm, "octave"))
+        idx = cellfun (@(S) strcmp (S.name, pdep_nm), installed_pkgs_lst);
+        installed_pkgs_lst{idx}.invdeps(end+1) = {installed_pkgs_lst{i}.name};
+      endif
+    endfor
+  endfor
+
+endfunction
--- a/scripts/pkg/private/unload_packages.m	Tue Jan 28 07:59:52 2020 -0800
+++ b/scripts/pkg/private/unload_packages.m	Sat Jan 25 15:26:07 2020 +0100
@@ -31,15 +31,13 @@
 function unload_packages (files, handle_deps, local_list, global_list)
 
   installed_pkgs_lst = installed_packages (local_list, global_list);
-  num_packages = length (installed_pkgs_lst);
+  num_packages = numel (installed_pkgs_lst);
+  ## Add inverse dependencies to field "invdeps" of installed_pkgs_lst
+  installed_pkgs_lst = get_inverse_dependencies (installed_pkgs_lst);
 
   ## Read package names and installdirs into a more convenient format.
-  pnames = pdirs = cell (1, num_packages);
-  for i = 1:num_packages
-    pnames{i} = installed_pkgs_lst{i}.name;
-    pdirs{i} = installed_pkgs_lst{i}.dir;
-    pdeps{i} = installed_pkgs_lst{i}.depends;
-  endfor
+  pnames = cellfun (@(x) x.name, installed_pkgs_lst, "UniformOutput", false);
+  pdirs = cellfun (@(x) x.dir, installed_pkgs_lst, "UniformOutput", false);
 
   ## Get the current octave path.
   p = strtrim (ostrsplit (path (), pathsep ()));
@@ -47,18 +45,45 @@
   ## Unload package_name1 ...
   dirs = {};
   desc = {};
-  for i = 1:length (files)
-    idx = strcmp (pnames, files{i});
-    if (! any (idx))
-      error ("package %s is not installed", files{i});
+  idx = find (ismember (pnames, files));
+  missing_pkgs = setdiff (files, pnames(idx));
+  if (! isempty (missing_pkgs))
+    missing_pkgs = strjoin (missing_pkgs, " & ");
+    error ("pkg: package(s): %s not installed", missing_pkgs);
+  endif
+  dirs = pdirs(idx);
+  desc = installed_pkgs_lst(idx);
+
+  if (handle_deps)
+    ## Check for loaded inverse dependencies of packages to be unloaded.
+    ## First create a list of loaded packages.
+    jdx = find (cellfun (@(x) x.loaded, installed_pkgs_lst));
+
+    ## Exclude packages requested to be unloaded
+    jdx = setdiff (jdx, idx);
+    loaded_pkgs = installed_pkgs_lst(jdx);
+    lpnames = pnames(jdx);
+    p2unload = pnames(idx);
+    linvdeps = {};
+    for i = 1:numel (desc)
+      ## Which inverse dependencies depend on this package-to-be-unloaded?
+      linvdeps = [linvdeps, get_inv_deps(desc{i}, loaded_pkgs, lpnames){:}];
+    endfor
+    if (! isempty (linvdeps))
+      linvdeps = unique (linvdeps);
+      txt = strjoin (linvdeps, "\n\t - ");
+      error (["pkg: the following loaded package(s):\n", ...
+              "\t - %s\n", ...
+              "depend on the one(s) you want to unload.\n", ...
+              "Either unload any depender package(s), or use the '-nodeps' flag.\n", ...
+              "Note: the '-nodeps' flag may affect functionality of these packages.\n"],
+             txt);
     endif
-    dirs{end+1} = pdirs{idx};
-    desc{end+1} = installed_pkgs_lst{idx};
-  endfor
+  endif
 
   ## Check for architecture dependent directories.
   archdirs = {};
-  for i = 1:length (dirs)
+  for i = 1:numel (dirs)
     tmpdir = getarchdir (desc{i});
     if (isfolder (tmpdir))
       archdirs{end+1} = dirs{i};
@@ -69,7 +94,7 @@
   endfor
 
   ## Unload the packages.
-  for i = 1:length (archdirs)
+  for i = 1:numel (archdirs)
     d = archdirs{i};
     idx = strcmp (p, d);
     if (any (idx))
@@ -79,3 +104,16 @@
   endfor
 
 endfunction
+
+
+function linvdeps = get_inv_deps (desc, loaded_pkgs, lpnames)
+
+  ## Which nested loaded inverse dependencies depend on the package in desc?
+  linvdeps = intersect (desc.invdeps, lpnames);
+  for i = 1:numel (linvdeps)
+    kdx = find (ismember (lpnames, linvdeps{i}));
+    linvdeps = [ linvdeps (get_inv_deps (loaded_pkgs{kdx}, ...
+                                         loaded_pkgs, lpnames)){:} ];
+  endfor
+
+endfunction