# HG changeset patch # User Philip Nienhuis # Date 1579962367 -3600 # Node ID 915b3630eed06610b456493e8c3e66c7bed10339 # Parent 306df6825dd99b3ee3d6213319b2cd06ccf92dc6 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. diff -r 306df6825dd9 -r 915b3630eed0 scripts/pkg/module.mk --- 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 \ diff -r 306df6825dd9 -r 915b3630eed0 scripts/pkg/pkg.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, diff -r 306df6825dd9 -r 915b3630eed0 scripts/pkg/private/describe.m --- 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"); diff -r 306df6825dd9 -r 915b3630eed0 scripts/pkg/private/get_inverse_dependencies.m --- /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 . +## +## 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 +## . +## +######################################################################## + +## -*- 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 diff -r 306df6825dd9 -r 915b3630eed0 scripts/pkg/private/unload_packages.m --- 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