1######################################################################## 2## 3## Copyright (C) 2005-2021 The Octave Project Developers 4## 5## See the file COPYRIGHT.md in the top-level directory of this 6## distribution or <https://octave.org/copyright/>. 7## 8## This file is part of Octave. 9## 10## Octave is free software: you can redistribute it and/or modify it 11## under the terms of the GNU General Public License as published by 12## the Free Software Foundation, either version 3 of the License, or 13## (at your option) any later version. 14## 15## Octave is distributed in the hope that it will be useful, but 16## WITHOUT ANY WARRANTY; without even the implied warranty of 17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18## GNU General Public License for more details. 19## 20## You should have received a copy of the GNU General Public License 21## along with Octave; see the file COPYING. If not, see 22## <https://www.gnu.org/licenses/>. 23## 24######################################################################## 25 26## -*- texinfo -*- 27## @deftypefn {} {} pkg @var{command} @var{pkg_name} 28## @deftypefnx {} {} pkg @var{command} @var{option} @var{pkg_name} 29## @deftypefnx {} {[@var{out1}, @dots{}] =} pkg (@var{command}, @dots{} ) 30## Manage or query packages (groups of add-on functions) for Octave. 31## 32## Packages can be installed globally (i.e., for all users of the system) or 33## locally (i.e., for the current user only). 34## 35## Global packages are installed by default in a system-wide location. This is 36## usually a subdirectory of the folder where Octave itself is installed. 37## Therefore, Octave needs write access to this folder to install global 38## packages. That usually means that Octave has to run with root access (or 39## "Run as administrator" on Windows) to be able to install packages globally. 40## 41## In contrast, local packages are installed by default in the user's 42## home directory (profile on Windows) and are only available to that specific 43## user. Usually, they can be installed without root access (or administrative 44## privileges). 45## 46## For global and local packages, there are separate databases holding the 47## information about the installed packages. If some package is installed 48## globally as well as locally, the local installation takes precedence over 49## ("shadows") the global one. Which package installation (global or local) is 50## used can also be manipulated by using prefixes and/or using the 51## @samp{local_list} input argument. Using these mechanisms, several different 52## releases of one and the same package can be installed side by side as well 53## (but cannot be loaded simultaneously). 54## 55## Packages might depend on external software and/or other packages. To be 56## able to install such packages, these dependencies should be installed 57## beforehand. A package that depends on other package(s) can still be 58## installed using the @option{-nodeps} flag. The effects of unsatisfied 59## dependencies on external software---like libraries---depends on the 60## individual package. 61## 62## Packages must be loaded before they can be used. When loading a package, 63## Octave performs the following tasks: 64## @enumerate 65## @item 66## If the package depends on other packages (and @code{pkg load} is called 67## without the @option{-nodeps} option), the package is not loaded 68## immediately. Instead, those dependencies are loaded first (recursively if 69## needed). 70## 71## @item 72## When all dependencies are satisfied, the package's subdirectories are 73## added to the search path. 74## @end enumerate 75## 76## This load order leads to functions that are provided by dependencies being 77## potentially shadowed by functions of the same name that are provided by 78## top-level packages. 79## 80## Each time, a package is added to the search path, initialization script(s) 81## for the package are automatically executed if they are provided by the 82## package. 83## 84## Depending on the value of @var{command} and on the number of requested 85## return arguments, @code{pkg} can be used to perform several tasks. 86## Possible values for @var{command} are: 87## 88## @table @samp 89## 90## @item install 91## Install named packages. For example, 92## 93## @example 94## pkg install image-1.0.0.tar.gz 95## @end example 96## 97## @noindent 98## installs the package found in the file @file{image-1.0.0.tar.gz}. The 99## file containing the package can be a URL, e.g., 100## 101## @example 102## pkg install 'http://somewebsite.org/image-1.0.0.tar.gz' 103## @end example 104## 105## @noindent 106## installs the package found in the given URL@. This 107## requires an internet connection and the cURL library. 108## 109## @noindent 110## @emph{Security risk}: no verification of the package is performed 111## before the installation. It has the same security issues as manually 112## downloading the package from the given URL and installing it. 113## 114## @noindent 115## @emph{No support}: the GNU Octave community is not responsible for 116## packages installed from foreign sites. For support or for 117## reporting bugs you need to contact the maintainers of the installed 118## package directly (see the @file{DESCRIPTION} file of the package) 119## 120## The @var{option} variable can contain options that affect the manner 121## in which a package is installed. These options can be one or more of 122## 123## @table @code 124## @item -nodeps 125## The package manager will disable dependency checking. With this option it 126## is possible to install a package even when it depends on another package 127## which is not installed on the system. @strong{Use this option with care.} 128## 129## @item -local 130## A local installation (package available only to current user) is forced, 131## even if the user has system privileges. 132## 133## @item -global 134## A global installation (package available to all users) is forced, even if 135## the user doesn't normally have system privileges. 136## 137## @item -forge 138## Install a package directly from the Octave Forge repository. This 139## requires an internet connection and the cURL library. 140## 141## @emph{Security risk}: no verification of the package is performed 142## before the installation. There are no signature for packages, or 143## checksums to confirm the correct file was downloaded. It has the 144## same security issues as manually downloading the package from the 145## Octave Forge repository and installing it. 146## 147## @item -verbose 148## The package manager will print the output of all commands as 149## they are performed. 150## @end table 151## 152## @item update 153## Check installed Octave Forge packages against repository and update any 154## outdated items. This requires an internet connection and the cURL library. 155## Usage: 156## 157## @example 158## pkg update 159## @end example 160## 161## @noindent 162## To update a single package use @code{pkg install -forge} 163## 164## @item uninstall 165## Uninstall named packages. For example, 166## 167## @example 168## pkg uninstall image 169## @end example 170## 171## @noindent 172## removes the @code{image} package from the system. If another installed 173## package depends on the @code{image} package an error will be issued. 174## The package can be uninstalled anyway by using the @option{-nodeps} option. 175## 176## @item load 177## Add named packages to the path. After loading a package it is 178## possible to use the functions provided by the package. For example, 179## 180## @example 181## pkg load image 182## @end example 183## 184## @noindent 185## adds the @code{image} package to the path. 186## 187## Note: When loading a package, @code{pkg} will automatically try to load 188## any unloaded dependencies as well, unless the @option{-nodeps} flag has 189## been specified. For example, 190## 191## @example 192## pkg load signal 193## @end example 194## 195## @noindent 196## adds the @code{signal} package and also tries to load its dependency: the 197## @code{control} package. Be aware that the functionality of package(s) 198## loaded will probably be impacted by use of the @option{-nodeps} flag. Even 199## if necessary dependencies are loaded later, the functionality of top-level 200## packages can still be affected because the optimal loading order may not 201## have been followed. 202## 203## @item unload 204## Remove named packages from the path. After unloading a package it is 205## no longer possible to use the functions provided by the package. Trying 206## to unload a package that other loaded packages still depend on will result 207## in an error; no packages will be unloaded in this case. A package can 208## be forcibly removed with the @option{-nodeps} flag, but be aware that the 209## functionality of dependent packages will likely be affected. As when 210## loading packages, reloading dependencies after having unloaded them with the 211## @option{-nodeps} flag may not restore all functionality of the dependent 212## packages as the required loading order may be incorrect. 213## 214## @item list 215## Show the list of currently installed packages. For example, 216## 217## @example 218## pkg list 219## @end example 220## 221## @noindent 222## will produce a short report with the package name, version, and installation 223## directory for each installed package. Supply a package name to limit 224## reporting to a particular package. For example: 225## 226## @example 227## pkg list image 228## @end example 229## 230## If a single return argument is requested then @code{pkg} returns a cell 231## array where each element is a structure with information on a single 232## package. 233## 234## @example 235## installed_packages = pkg ("list") 236## @end example 237## 238## If two output arguments are requested @code{pkg} splits the list of 239## installed packages into those which were installed by the current user, 240## and those which were installed by the system administrator. 241## 242## @example 243## [user_packages, system_packages] = pkg ("list") 244## @end example 245## 246## The @qcode{"-forge"} option lists packages available at the Octave Forge 247## repository. This requires an internet connection and the cURL library. 248## For example: 249## 250## @example 251## oct_forge_pkgs = pkg ("list", "-forge") 252## @end example 253## 254## @item describe 255## Show a short description of installed packages. With the option 256## @qcode{"-verbose"} also list functions provided by the package. For 257## example, 258## 259## @example 260## pkg describe -verbose 261## @end example 262## 263## @noindent 264## will describe all installed packages and the functions they provide. 265## Display can be limited to a set of packages: 266## 267## @example 268## @group 269## ## describe control and signal packages 270## pkg describe control signal 271## @end group 272## @end example 273## 274## If one output is requested a cell of structure containing the 275## description and list of functions of each package is returned as 276## output rather than printed on screen: 277## 278## @example 279## desc = pkg ("describe", "secs1d", "image") 280## @end example 281## 282## @noindent 283## If any of the requested packages is not installed, @code{pkg} returns an 284## error, unless a second output is requested: 285## 286## @example 287## [desc, flag] = pkg ("describe", "secs1d", "image") 288## @end example 289## 290## @noindent 291## @var{flag} will take one of the values @qcode{"Not installed"}, 292## @qcode{"Loaded"}, or 293## @qcode{"Not loaded"} for each of the named packages. 294## 295## @item prefix 296## Set the installation prefix directory. For example, 297## 298## @example 299## pkg prefix ~/my_octave_packages 300## @end example 301## 302## @noindent 303## sets the installation prefix to @file{~/my_octave_packages}. 304## Packages will be installed in this directory. 305## 306## It is possible to get the current installation prefix by requesting an 307## output argument. For example: 308## 309## @example 310## pfx = pkg ("prefix") 311## @end example 312## 313## The location in which to install the architecture dependent files can be 314## independently specified with an addition argument. For example: 315## 316## @example 317## pkg prefix ~/my_octave_packages ~/my_arch_dep_pkgs 318## @end example 319## 320## @item local_list 321## Set the file in which to look for information on locally 322## installed packages. Locally installed packages are those that are 323## available only to the current user. For example: 324## 325## @example 326## pkg local_list ~/.octave_packages 327## @end example 328## 329## It is possible to get the current value of local_list with the following 330## 331## @example 332## pkg local_list 333## @end example 334## 335## @item global_list 336## Set the file in which to look for information on globally 337## installed packages. Globally installed packages are those that are 338## available to all users. For example: 339## 340## @example 341## pkg global_list /usr/share/octave/octave_packages 342## @end example 343## 344## It is possible to get the current value of global_list with the following 345## 346## @example 347## pkg global_list 348## @end example 349## 350## @item build 351## Build a binary form of a package or packages. The binary file produced 352## will itself be an Octave package that can be installed normally with 353## @code{pkg}. The form of the command to build a binary package is 354## 355## @example 356## pkg build builddir image-1.0.0.tar.gz @dots{} 357## @end example 358## 359## @noindent 360## where @code{builddir} is the name of a directory where the temporary 361## installation will be produced and the binary packages will be found. 362## The options @option{-verbose} and @option{-nodeps} are respected, while 363## all other options are ignored. 364## 365## @item rebuild 366## Rebuild the package database from the installed directories. This can 367## be used in cases where the package database has been corrupted. 368## 369## @item test 370## Perform the built-in self tests contained in all functions provided by 371## the named packages. For example: 372## 373## @example 374## pkg test image 375## @end example 376## 377## @end table 378## @seealso{ver, news} 379## @end deftypefn 380 381function [local_packages, global_packages] = pkg (varargin) 382 383 ## Installation prefix 384 persistent user_prefix = false; 385 persistent prefix = false; 386 persistent archprefix = -1; 387 persistent local_list = tilde_expand (fullfile ("~", ".octave_packages")); 388 persistent global_list = fullfile (OCTAVE_HOME (), "share", "octave", 389 "octave_packages"); 390 391 ## If user is superuser (posix) or the process has elevated rights (Windows), 392 ## set global_install to true. 393 if (ispc () && ! isunix ()) 394 global_install = __is_elevated_process__ (); 395 else 396 global_install = (geteuid () == 0); 397 endif 398 399 if (! user_prefix) 400 [prefix, archprefix] = default_prefix (global_install); 401 prefix = tilde_expand (prefix); 402 archprefix = tilde_expand (archprefix); 403 endif 404 405 mlock (); 406 407 confirm_recursive_rmdir (false, "local"); 408 409 # valid actions in alphabetical order 410 available_actions = {"build", "describe", "global_list", "install", ... 411 "list", "load", "local_list", "prefix", "rebuild", ... 412 "test", "uninstall", "unload", "update"}; 413 414 ## Parse input arguments 415 if (isempty (varargin) || ! iscellstr (varargin)) 416 print_usage (); 417 endif 418 files = {}; 419 deps = true; 420 action = "none"; 421 verbose = false; 422 octave_forge = false; 423 for i = 1:numel (varargin) 424 switch (varargin{i}) 425 case "-nodeps" 426 deps = false; 427 ## TODO completely remove these warnings after some releases. 428 case "-noauto" 429 warning ("Octave:deprecated-option", 430 ["pkg: autoload is no longer supported. The -noauto "... 431 "option is no longer required."]); 432 case "-auto" 433 warning ("Octave:deprecated-option", 434 ["pkg: autoload is no longer supported. Add a "... 435 "'pkg load ...' command to octaverc instead."]); 436 case "-verbose" 437 verbose = true; 438 ## Send verbose output to pager immediately. Change setting locally. 439 page_output_immediately (true, "local"); 440 case "-forge" 441 if (! __octave_config_info__ ("CURL_LIBS")) 442 error ("pkg: can't download from Octave Forge without the cURL library"); 443 endif 444 octave_forge = true; 445 case "-local" 446 global_install = false; 447 if (! user_prefix) 448 [prefix, archprefix] = default_prefix (global_install); 449 endif 450 case "-global" 451 global_install = true; 452 if (! user_prefix) 453 [prefix, archprefix] = default_prefix (global_install); 454 endif 455 case available_actions 456 if (! strcmp (action, "none")) 457 error ("pkg: more than one action specified"); 458 endif 459 action = varargin{i}; 460 otherwise 461 files{end+1} = varargin{i}; 462 endswitch 463 endfor 464 465 if (octave_forge && ! any (strcmp (action, {"install", "list"}))) 466 error ("pkg: '-forge' can only be used with install or list"); 467 endif 468 469 ## Take action 470 switch (action) 471 case "list" 472 if (octave_forge) 473 if (nargout) 474 local_packages = list_forge_packages (); 475 else 476 list_forge_packages (); 477 endif 478 else 479 if (nargout == 1) 480 local_packages = installed_packages (local_list, global_list, files); 481 elseif (nargout > 1) 482 [local_packages, global_packages] = installed_packages (local_list, 483 global_list, 484 files); 485 else 486 installed_packages (local_list, global_list, files); 487 endif 488 endif 489 490 case "install" 491 if (isempty (files)) 492 error ("pkg: install action requires at least one filename"); 493 endif 494 495 local_files = {}; 496 tmp_dir = tempname (); 497 unwind_protect 498 499 if (octave_forge) 500 [urls, local_files] = cellfun ("get_forge_download", files, 501 "uniformoutput", false); 502 [files, succ] = cellfun ("urlwrite", urls, local_files, 503 "uniformoutput", false); 504 succ = [succ{:}]; 505 if (! all (succ)) 506 i = find (! succ, 1); 507 error ("pkg: could not download file %s from URL %s", 508 local_files{i}, urls{i}); 509 endif 510 else 511 ## If files do not exist, maybe they are not local files. 512 ## Try to download them. 513 not_local_files = cellfun (@(x) isempty (glob (x)), files); 514 if (any (not_local_files)) 515 [success, msg] = mkdir (tmp_dir); 516 if (success != 1) 517 error ("pkg: failed to create temporary directory: %s", msg); 518 endif 519 520 for file = files(not_local_files) 521 file = file{1}; 522 [~, fname, fext] = fileparts (file); 523 tmp_file = fullfile (tmp_dir, [fname fext]); 524 local_files{end+1} = tmp_file; 525 looks_like_url = regexp (file, '^\w+://'); 526 if (looks_like_url) 527 [~, success, msg] = urlwrite (file, local_files{end}); 528 if (success != 1) 529 error ("pkg: failed downloading '%s': %s", file, msg); 530 endif 531 ## Verify that download is a tarball, 532 ## to protect against ISP DNS hijacking. 533 ## FIXME: Need a test which does not rely on external OS. 534 #{ 535 if (isunix ()) 536 [ok, file_descr] = ... 537 system (sprintf ('file "%s" | cut -d ":" -f 2', ... 538 local_files{end})); 539 if (! ok) 540 if (strfind (file_descr, "HTML")) 541 error (["pkg: Invalid package file downloaded from " ... 542 "%s\n" ... 543 "File is HTML, not a tar archive."], ... 544 file); 545 endif 546 else 547 ## Ignore: maybe something went wrong with the "file" call. 548 endif 549 endif 550 #} 551 else 552 looks_like_pkg_name = regexp (file, '^[\w-]+$'); 553 if (looks_like_pkg_name) 554 error (["pkg: file not found: %s.\n" ... 555 "This looks like an Octave Forge package name." ... 556 " Did you mean:\n" ... 557 " pkg install -forge %s"], ... 558 file, file); 559 else 560 error ("pkg: file not found: %s", file); 561 endif 562 endif 563 files{strcmp (files, file)} = local_files{end}; 564 565 endfor 566 endif 567 endif 568 install (files, deps, prefix, archprefix, verbose, local_list, 569 global_list, global_install); 570 571 unwind_protect_cleanup 572 cellfun ("unlink", local_files); 573 if (exist (tmp_dir, "file")) 574 rmdir (tmp_dir, "s"); 575 endif 576 end_unwind_protect 577 578 case "uninstall" 579 if (isempty (files)) 580 error ("pkg: uninstall action requires at least one package name"); 581 endif 582 uninstall (files, deps, verbose, local_list, global_list, global_install); 583 584 case "load" 585 if (isempty (files)) 586 error ("pkg: load action requires at least one package name"); 587 endif 588 load_packages (files, deps, local_list, global_list); 589 590 case "unload" 591 if (isempty (files)) 592 error ("pkg: unload action requires at least one package name"); 593 endif 594 unload_packages (files, deps, local_list, global_list); 595 596 case "prefix" 597 if (isempty (files) && ! nargout) 598 printf ("Installation prefix: %s\n", prefix); 599 printf ("Architecture dependent prefix: %s\n", archprefix); 600 elseif (isempty (files) && nargout) 601 local_packages = prefix; 602 global_packages = archprefix; 603 elseif (numel (files) >= 1 && ischar (files{1})) 604 prefix = tilde_expand (files{1}); 605 local_packages = prefix = make_absolute_filename (prefix); 606 user_prefix = true; 607 if (numel (files) >= 2 && ischar (files{2})) 608 archprefix = make_absolute_filename (tilde_expand (files{2})); 609 endif 610 else 611 error ("pkg: prefix action requires a directory input, or an output argument"); 612 endif 613 614 case "local_list" 615 if (isempty (files) && ! nargout) 616 disp (local_list); 617 elseif (isempty (files) && nargout) 618 local_packages = local_list; 619 elseif (numel (files) == 1 && ! nargout && ischar (files{1})) 620 local_list = tilde_expand (files{1}); 621 if (! exist (local_list, "file")) 622 try 623 ## Force file to be created 624 fclose (fopen (local_list, "wt")); 625 catch 626 error ("pkg: cannot create file %s", local_list); 627 end_try_catch 628 endif 629 local_list = canonicalize_file_name (local_list); 630 else 631 error ("pkg: specify a local_list file, or request an output argument"); 632 endif 633 634 case "global_list" 635 if (isempty (files) && ! nargout) 636 disp (global_list); 637 elseif (isempty (files) && nargout) 638 local_packages = global_list; 639 elseif (numel (files) == 1 && ! nargout && ischar (files{1})) 640 global_list = files{1}; 641 if (! exist (global_list, "file")) 642 try 643 ## Force file to be created 644 fclose (fopen (files{1}, "wt")); 645 catch 646 error ("pkg: cannot create file %s", global_list); 647 end_try_catch 648 endif 649 global_list = canonicalize_file_name (global_list); 650 else 651 error ("pkg: specify a global_list file, or request an output argument"); 652 endif 653 654 case "rebuild" 655 if (global_install) 656 global_packages = rebuild (prefix, archprefix, global_list, files, 657 verbose); 658 global_packages = save_order (global_packages); 659 if (ispc) 660 ## On Windows ensure LFN paths are saved rather than 8.3 style paths 661 global_packages = standardize_paths (global_packages); 662 endif 663 global_packages = make_rel_paths (global_packages); 664 save (global_list, "global_packages"); 665 if (nargout) 666 local_packages = global_packages; 667 endif 668 else 669 local_packages = rebuild (prefix, archprefix, local_list, files, 670 verbose); 671 local_packages = save_order (local_packages); 672 if (ispc) 673 local_packages = standardize_paths (local_packages); 674 endif 675 save (local_list, "local_packages"); 676 if (! nargout) 677 clear ("local_packages"); 678 endif 679 endif 680 681 case "build" 682 if (numel (files) < 2) 683 error ("pkg: build action requires build directory and at least one filename"); 684 endif 685 build (files{1}, files(2:end), verbose); 686 687 case "describe" 688 ## FIXME: name of the output variables is inconsistent with their content 689 if (nargout) 690 [local_packages, global_packages] = describe (files, verbose, 691 local_list, global_list); 692 else 693 describe (files, verbose, local_list, global_list); 694 endif 695 696 case "update" 697 installed_pkgs_lst = installed_packages (local_list, global_list); 698 699 ## Explicit list of packages to update, rather than all packages 700 if (numel (files) > 0) 701 update_lst = {}; 702 installed_names = cellfun (@(idx) idx.name, installed_pkgs_lst, 703 "UniformOutput", false); 704 for i = 1:numel (files) 705 idx = find (strcmp (files{i}, installed_names), 1); 706 if (isempty (idx)) 707 warning ("pkg: package %s is not installed - skipping update", 708 files{i}); 709 else 710 update_lst = [ update_lst, installed_pkgs_lst(idx) ]; 711 endif 712 endfor 713 installed_pkgs_lst = update_lst; 714 endif 715 716 for i = 1:numel (installed_pkgs_lst) 717 installed_pkg_name = installed_pkgs_lst{i}.name; 718 installed_pkg_version = installed_pkgs_lst{i}.version; 719 try 720 forge_pkg_version = get_forge_pkg (installed_pkg_name); 721 catch 722 warning ("pkg: package %s not found on Octave Forge - skipping update\n", 723 installed_pkg_name); 724 forge_pkg_version = "0"; 725 end_try_catch 726 if (compare_versions (forge_pkg_version, installed_pkg_version, ">")) 727 feval (@pkg, "install", "-forge", installed_pkg_name); 728 endif 729 endfor 730 731 case "test" 732 if (isempty (files)) 733 error ("pkg: test action requires at least one package name"); 734 endif 735 ## Make sure the requested packages are loaded 736 orig_path = path (); 737 load_packages (files, deps, local_list, global_list); 738 ## Test packages one by one 739 installed_pkgs_lst = installed_packages (local_list, global_list, files); 740 unwind_protect 741 for i = 1:numel (installed_pkgs_lst) 742 printf ("Testing functions in package '%s':\n", files{i}); 743 installed_pkgs_dirs = {installed_pkgs_lst{i}.dir, ... 744 installed_pkgs_lst{i}.archprefix}; 745 installed_pkgs_dirs = ... 746 installed_pkgs_dirs (! cellfun (@isempty, installed_pkgs_dirs)); 747 ## For local installs installed_pkgs_dirs contains the same subdirs 748 installed_pkgs_dirs = unique (installed_pkgs_dirs); 749 if (! isempty (installed_pkgs_dirs)) 750 ## FIXME invoke another test routine once that is available. 751 ## Until then __run_test_suite__.m will do the job fine enough 752 __run_test_suite__ ({installed_pkgs_dirs{:}}, {}); 753 endif 754 endfor 755 unwind_protect_cleanup 756 ## Restore load path back to its original value before loading packages 757 path (orig_path); 758 end_unwind_protect 759 760 otherwise 761 error ("pkg: invalid action. See 'help pkg' for available actions"); 762 endswitch 763 764endfunction 765