1 /*
2  * pkg_mngr.i
3  * $Id: pkg_mngr.i,v 1.19 2008-11-18 13:47:27 paumard Exp $
4  * Yorick package manager
5  */
6 /* Copyright (c) 2005, The Regents of the University of California.
7  * All rights reserved.
8  * This file is part of yorick (http://yorick.sourceforge.net).
9  * Read the accompanying LICENSE file for details.
10  */
11 
12 /*= SECTION() yorick package manager ======================================*/
13 
14 PKG_MNGR_VERSION = 0.7;
15 
16 if (!PKG_SETUP) PKG_SETUP = Y_HOME+"packages/pkg_setup.i";
17 
18 local pkg_mngr;
19 /* DOCUMENT pkg_mngr.i
20  *
21  * Yorick Package Manager. Main functions
22  *
23  * pkg_setup               Set up pkg_mngr parameters (see below for details)
24  * pkg_sync                Sync the local package repository with server
25  * pkg_list                List all available packages (list install status
26  *                         ("i" marks installed packages), the package name,
27  *                         the last available version, the installed version
28  *                         (when applicable) and a short description.
29  * pkg_install,"pkg_name"  Install package "pkg_name"
30  * pkg_upgrade,"pkg_name"  Upgrade package "pkg_name"
31  * pkg_remove,"pkg_name"   Remove package "pkg_name"
32  * pkg_info,"pkg_name"     Print detail info about package "pkg_name"
33  * pkg_save                Saves PKG global variables in default file,
34  *                          which will be re-read each time pkg-mngr.i
35  *                          is included. Called by pkg_setup.
36  * pkg_reset               Delete all tarballs (in case of problem).
37  *
38  *  --- INTRODUCTION TO THE YORICK PACKAGE INSTALLER
39  *
40  *  --- TYPICAL USE
41  *
42  *  FIRST TIME USE:
43  *
44  *  > pkg_setup
45  *  ... will ask for parameters for your OS/installation
46  *  ... just set the OS, most of the other defaults should be OK.
47  *
48  *   PKG_FETCH_CMD       : system cmd to fetch a file through an
49  *                          internet connection [ex: curl]
50  *   PKG_SERVER          : URL of central server with info files
51  *   PKG_OS              : OSTYPE-ARCHTYPE. There is a limited number
52  *                          of these available. pkg_setup will fetch
53  *                          available value from the server and propose
54  *                          a list to the user.
55  *   PKG_GUNTAR_CMD      : system cmd to gunzip and untar a tgz file
56  *                          [ex; "tar -zxf"]
57  *   PKG_TMP_DIR         : temporary directory. Normaly Y_HOME/packages/tmp
58  *   PKG_VERBOSE         : verbose level
59  *   PKG_ASK_CONFIRM     : ask for confirmation in critical operations
60  *   PKG_RUN_CHECK       : run check after install
61  *
62  *  > pkg_sync
63  *  ...to sync your local info file repository with the server
64  *  ...this should be done from time to time.
65  *
66  *  ---
67  *
68  *  > pkg_list
69  *  ...fetch all packages .info files on the server, and prints out
70  *  the available & installed packages, with their version.
71  *
72  *  > pkg_install,"pkgname"
73  *  ... will install package "pkgname"
74  *
75  *  FURTHER USE:
76  * pkg_list, pkg_install, and pkg_remove are the only 3 functions
77  * that should be necessary for further use.
78  *
79  *  --- IN CASE OF PROBLEMS:
80  *  In case something went wrong and you downloaded a bad tarball:
81  *  Solution 1: try reloading with force=1
82  *  Solution 2: Go in Y_HOME/packages/tarballs
83  *  and remove the offensive tgz file. In doubt, you can just wipe
84  *  up the whole directory content. tarballs will be downloaded again
85  *  if pkg_mngr does not find existing local ones.
86  *
87  *  --- SERVER / CLIENT ORGANIZATION
88  *
89  *  The yorick package manager (pkg_mngr.i) is organized with several
90  *  central servers (sourceforge, maumae), being repositories of binary
91  *  packages. These packages are self-contained plugins for yorick, and
92  *  include yorick include files, autoload files and libraries. Necessary
93  *  dependencies have been linked in. Other yorick package dependecies
94  *  are cared for, and dependent packages are installed automatically.
95  *
96  *  Locally, the directory package is the home of the packager. It contains
97  *  3 subdirectories:
98  *  packages/info: home of the info files that describe all available
99  *                 packages.
100  *  packages/installed: info files for installed packages.
101  *  packages/tarballs:  tarballs for installed packages.
102  *
103  *  --- PACKAGE STRUCTURE:
104  *
105  *  A standard package includes (once tar zxvf'd):
106  *
107  *
108  *  root/
109  *  root/pkg.info         : package info file
110  *  root/preflight.i      : if present, will be run, from Y_HOME/packages,
111  *                           before the copying of the other files (but
112  *                           after the tar -zxvf or equivalent).
113  *  root/postflight.i     : if present, will be run, from Y_HOME/packages,
114  *                           after the copying of the other files (but
115  *                           before the dist tree cleanup).
116  *  root/dist/            : distribution root
117  *  root/dist/y_site/     : all the files under this will be copied
118  *                           recursively into Y_SITE.
119  *  root/dist/y_home/     : all the files under this will be copied
120  *                           recursively into Y_HOME.
121  *
122  *  I recommend to include and distribute a copy of the package source:
123  *  It is (1) educational, (2) easier to rebuild if needed and (3) that's
124  *  a good place to store a check.i and other example for the package (and
125  *  perhaps a doc). A good place to store the source is Y_SITE/contrib/pkg.
126  *
127  *  Here is an example for the HDF5 package:
128  *
129  *  poliahu:tarballs% tar zxvf hdf5-0.5.tgz
130  *  hdf5/
131  *  hdf5/dist/
132  *  hdf5/dist/check.i
133  *  hdf5/dist/y_home/
134  *  hdf5/dist/y_home/i-start/
135  *  hdf5/dist/y_home/i-start/yhdf5.i
136  *  hdf5/dist/y_home/lib/
137  *  hdf5/dist/y_home/lib/hdf5.so
138  *  hdf5/dist/y_site/
139  *  hdf5/dist/y_site/contrib/
140  *  hdf5/dist/y_site/contrib/hdf5/
141  *  hdf5/dist/y_site/contrib/hdf5/check.i
142  *  hdf5/dist/y_site/contrib/hdf5/hdf5.c
143  *  hdf5/dist/y_site/contrib/hdf5/hdf5.i
144  *  hdf5/dist/y_site/contrib/hdf5/Makefile
145  *  hdf5/dist/y_site/contrib/hdf5/yhdf5.i
146  *  hdf5/dist/y_site/i0/
147  *  hdf5/dist/y_site/i0/hdf5.i
148  *  hdf5/hdf5.info
149  *
150  *  --- INSTALLER MECHANICS
151  *
152  *  The operation of installing a packages goes through two main parts:
153  *
154  *  part 1. Get the tarball from the server.
155  *
156  *  part 2. Once the tarball is local, it is installed. All the libraries
157  *    and other files are put where they belong. Usually:
158  *    libraries (*.so) are put in Y_HOME/lib
159  *    include files (*.i) are put in Y_SITE/i0
160  *    autoload files are put in Y_HOME/i-start
161  *    However, this is not mandatory, and depends on how the maintainer
162  *    has arrange the files in the tarball.
163  *    The installer also scans for a preflight and postflight include files,
164  *    and run them (include them) if present.
165  *
166  *  In more details:
167  *
168  *  pkgname example = hdf5;
169  *
170  *  I.  check for depenency tree. Announce dependencies, possibly
171  *      ask for confirm.
172  *  II. Start by lowest dependency:
173  *     1.  check if already on disk locally [to come]
174  *     2.  if yes, check that local version is the last one [to come]
175  *     3.  if not, offer the choice to re-install local version
176  *           or fetch and install new one [to come]
177  *     4.  fetch tarball (if necessary)
178  *     5.  md5 it [to come]
179  *     6.  gunzip + untar it (e.g. tar -zxvf)
180  *     7.  cd in pkgname directory
181  *     8.  check if preflight exist. If it does, execute it (include it).
182  *     9.  recursively copy the distributed files in Y_SITE and Y_HOME
183  *     10. copy the info file in the installed directory, with a list
184  *           of the installed files.
185  *     11. check if postflight exist. If it does, execute it (include it).
186  *     12. clean up after ourselves (temp dist tree). Keep tarball.
187  *
188  *  III. Update installed pkg. This is done automatically by putting
189  *       the info file of the installed package in package/installed
190  *
191  *
192  *  --- .INFO FILE
193  *
194  *  Here is an example of a .info file (from the hdf5 package):
195  *
196  *  Package: hdf5
197  *  Kind: plugin
198  *  Version: 0.5
199  *  Revision: 1
200  *  Description: Hierarchical Data Format 5 interface
201  *  License: GPL
202  *  Maintainer: Francois Rigaut <frigaut@users.sf.net>
203  *  OS: macosx
204  *  Depends: yorick(>=1.6.02)
205  *  Source: http://www.maumae.net/yorick/packages/%o/tarballs/hdf5-%v.tgz
206  *  Source-MD5: 6f8038cd09f72f4ff060e2d278256b6f
207  *  Source-Directory: contrib/hdf5
208  *  DocFiles: README NEWS doc/*.doc doc/*.pdf doc/*.ps doc/*.tex
209  *  Homepage: http://www.maumae.net/yorick/doc/plugins.php
210  *  DescDetail: <<
211  *  HDF5 is the yorick interface plugin to the NCSA Hierarchical Data Format
212  *  version 5. It includes function for reading, writing, updating, getting
213  *  information on HDF5 files.
214  *  <<
215  *  DescUsage: <<
216  *  See i/hdf5_tests.i for a test suite. Type
217  *  "yorick -batch hdf5_tests.i" in a terminal to run it.
218  *  <<
219  *  DescPort: <<
220  *  This package will compile Yorick only on MacOSX 10.3.4 or later, because
221  *  of a bug in the system math library libm (part of /usr/lib/LibSystem.dylib)
222  *  in earlier versions of MacOSX 10.3.
223  *  <<
224  *
225  *  Right now, the only important (used) keywords are:
226  *  Version, Description, OS, Depends, Source
227  *
228  *  If you want to contribute a package, you will have to generate a
229  *  .info file:
230  *
231  *  Instructions and tips:
232  *  - Keep the description short (< 45 characters), you can expand ad lib in
233  *    the DescDetail
234  *  - Depends can include several members, separated by a comma (e.g.
235  *    yorick(>=1.6.02),imutil(>0.4)
236  *  - OS has to be "macosx","linux" or "windows" for now. lowercase pls.
237  *  - Source if the URL where the tarball can be fetched. In the near future,
238  *    I'm planning to make that compatible with a vector (several URL that
239  *    will be tried after another in case a link is down) but for now,
240  *    keep that a single URL.
241  *  - Version can only contain integer (e.g. 2.1.4-r2 does *not* work).
242  *
243  *
244  *  --- HISTORY
245  *
246  *  * v. 0.7, Sun, 21 May 2006 19:00:55 +0200
247  *    Francois Rigaut & Thibaut Paumard
248  *    Allow installations in other locations than the Y_SITE and Y_HOME.
249  *    Some users may not have access to these and still want to install
250  *    packages for their personnal use.
251  *
252  *  --- TO DO
253  *
254  *  * Check documentation relative to new functionalities in v. 0.7
255  *
256  *  * At one point, we should have, to complement this installer, a
257  *    utility to check for symbols conflicts (e.g. same function names
258  *    in 2 different packages)
259  *
260  */
261 
262 require,"string.i";
263 require,"pathfun.i";
264 
265 struct pkginfo_str{
266   string name;
267   string kind;
268   string vers;
269   string revision;
270   string desc;
271   string license;
272   string maintainer;
273   string os;
274   string depends;
275   string depends_pkg(20);
276   string depends_vers(20);
277   string depends_rel(20);
278   string source;
279   string md5;
280   string dir;
281   string docs;
282   string homepage;
283   string desc_details;
284   long   version(20);
285 };
286 
has_write_permissions(dir)287 func has_write_permissions(dir)
288 /* DOCUMENT has_write_permissions(dir)
289    Test if user has write permission in "dir"
290    returns 1 if the user has write permission, 0 if not.
291    Create and delete a file "test_permissions_$USER", with
292    $USER = name of the user as defined in this session environment
293    SEE ALSO:
294  */
295 {
296   f=open(dir+"test_permissions_"+get_env("USER"),"w",1);
297   if (!f) return 0;
298   close,f;
299   remove,dir+"test_permissions_"+get_env("USER");
300   return 1;
301 }
302 
303 setup_done=0;        // check is setup variables have been set
304 user_setup_done=0;   // has the set-up been done at the user level?
305 
306 // read packages/pkg_setup.i if it exists
307 if (open(PKG_SETUP,"r",1)) {
308   extern pkg_other_installed;
309   require,PKG_SETUP;
310 
311   // start FR: check the pkg_setup just read is the user one.
312   if (has_write_permissions(dirname(PKG_SETUP))) {
313     // all OK: This user has permission on this part of the file system.
314     // If this was not true, then most likely the user has not done
315     // his/her own pkg_setup, but is reading the system one. In that
316     // case, a pkg_sync or pkg_install is likely to end up in error.
317     user_setup_done=1;
318   } else { // else user_setup_done=0 above is kept.
319     write,format="%s\n","Use \"pkg_setup\" to set up pkg_mngr";
320   }
321   // end FR
322 
323   // TP: make sure we didn't load an empty file
324   if (PKG_OS) {
325     // backward compatibility
326     if (!PKG_VAR_STATE) PKG_VAR_STATE = Y_HOME+"packages/";
327     if (!PKG_Y_HOME) PKG_Y_HOME = Y_HOME;
328     if (!PKG_Y_SITE) PKG_Y_SITE = Y_SITE;
329     if (!PKG_OTHER_INSTALLED) PKG_OTHER_INSTALLED = "";
330     pkg_other_installed=pathsplit(PKG_OTHER_INSTALLED);
331     setup_done=1;
332   } else {
333     write,format="Warning: Empty file \"%s\"\n",PKG_SETUP;
334   } // end BC
335   // end TP
336 } else { // else no set-up file found.
337   write,format="%s\n","Use \"pkg_setup\" to set up pkg_mngr";
338 }
339 
340 
341 func pkg_save
342 /* DOCUMENT pkg_save
343    Save the packager parameters in the file
344    Y_HOME/packages/pkg_setup.i
345    SEE ALSO: pkg_setup
346  */
347 {
348   mkdirp,dirname(PKG_SETUP);
349   f = open(PKG_SETUP,"w",1);
350   if (!f) error,"Can't create "+PKG_SETUP+" (permissions?)";
351   write,f,format="PKG_OS = \"%s\";\n",PKG_OS;
352   write,f,format="PKG_FETCH_CMD = \"%s\";\n",PKG_FETCH_CMD;
353   write,f,format="PKG_SERVER = \"%s\";\n",PKG_SERVER;
354   write,f,format="PKG_GUNTAR_CMD = \"%s\";\n",PKG_GUNTAR_CMD;
355   write,f,format="PKG_TMP_DIR = \"%s\";\n",PKG_TMP_DIR;
356   write,f,format="PKG_VERBOSE = %d;\n",PKG_VERBOSE;
357   write,f,format="PKG_ASK_CONFIRM = %d;\n",PKG_ASK_CONFIRM;
358   write,f,format="PKG_RUN_CHECK = %d;\n",PKG_RUN_CHECK;
359   // TP: here I add my new variables (paths)
360   write,f,format="PKG_VAR_STATE = \"%s\";\n",PKG_VAR_STATE;
361   write,f,format="PKG_Y_HOME = \"%s\";\n",PKG_Y_HOME;
362   write,f,format="PKG_Y_SITE = \"%s\";\n",PKG_Y_SITE;
363   write,f,format="PKG_OTHER_INSTALLED = \"%s\";\n",PKG_OTHER_INSTALLED;
364   // end_TP
365   if (PKG_SYNC_DONE) write,f,format="PKG_SYNC_DONE = \"%s\";\n",PKG_SYNC_DONE;
366   close,f;
367 
368   if (PKG_VERBOSE) write,format="%s\n","Parameters saved in "+PKG_SETUP;
369 }
370 
371 
372 
373 func pkg_sync(server,verbose=)
374 /* DOCUMENT pkg_sync(server)
375    sync the info tree with the central server
376    server: overrides the PKG_SERVER variable definition
377    SEE ALSO: pkg_mngr, pkg_list, pkg_install
378  */
379 {
380   extern PKG_SYNC_DONE;
381   if (!(setup_done & user_setup_done)) pkg_setup,first=1;
382 
383   if (!server) server = PKG_SERVER;
384   if (verbose==[]) verbose=PKG_VERBOSE;
385 
386   mkdirp,PKG_VAR_STATE;
387 
388   if (noneof(lsdir(PKG_VAR_STATE)=="info"))
389     mkdir,PKG_VAR_STATE+"info";
390   if (noneof(lsdir(PKG_VAR_STATE)=="installed"))
391     mkdir,PKG_VAR_STATE+"installed";
392   if (noneof(lsdir(PKG_VAR_STATE)=="tarballs"))
393     mkdir,PKG_VAR_STATE+"tarballs";
394   if (noneof(lsdir(PKG_VAR_STATE)=="tmp"))
395     mkdir,PKG_VAR_STATE+"tmp";
396 
397 
398   if (verbose) write,format="%s","Syncing with server";
399   if (verbose>=3) write,"";
400 
401 
402   pkg_fetch_url,server+PKG_OS+"/info/",PKG_TMP_DIR+"info.html",
403     verbose=verbose;
404 
405   if (verbose<3) write,format="%s",".";
406 
407   ctn = rdfile(PKG_TMP_DIR+"info.html");
408   files = strpart(ctn,strgrep("[a-zA-Z0-9\_\.\+\-]+\\.info",ctn));
409   files = files(where(files));
410 
411 
412   for (i=1;i<=numberof(files);i++) {
413     pkg_fetch_url,server+PKG_OS+"/info/"+files(i),      \
414       PKG_VAR_STATE+"info/"+files(i),verbose=verbose;
415     if (verbose<3) write,format="%s",".";
416   }
417 
418   if (verbose<3) write,format="done (%d info files fetched)\n",numberof(files);
419   PKG_SYNC_DONE = getdate();
420   pkg_save;
421 }
422 
get_international_date(date)423 func get_international_date(date)
424 /* DOCUMENT get_intern_date(date)
425    meant to convert date returned by getdate() into something
426    clear and understandable by people from europe and US
427    SEE ALSO:
428  */
429 {
430   if (!date) return;
431   d = strtok(date,"/",3);
432   n = 0;
433   sread,d(2),n;
434   month = ["Jan","Feb","Mar","Apr","May","Jun",
435            "Jul","Aug","Sep","Oct","Nov","Dec"];
436   return d(1)+" "+month(n)+" 20"+d(3); //valid for 95 years
437 }
438 
439 func pkg_list(server,sync=,verbose=)
440 /* DOCUMENT pkg_list,server,sync=,verbose=
441    Print out a list of available packages, including
442    version number, whether it is installed (installed
443    version), and a short description.
444    server: overrides the PKG_SERVER variable definition
445    sync= : if set, equivalent to "pkg_sync; pkg_list;"
446    SEE ALSO: pkg_mngr, pkg_sync, pkg_install
447  */
448 {
449   extern PKG_SYNC_DONE;
450   if (!setup_done) pkg_setup,first=1; // can list if user_setup not done
451   if (!PKG_SYNC_DONE) pkg_sync;
452 
453   if (sync) {
454     pkg_sync,server,verbose=verbose;
455   } else {
456     write,format="last sync %s (\"pkg_sync\" to sync)\n",get_international_date(PKG_SYNC_DONE);
457   }
458   if (verbose==[]) verbose=PKG_VERBOSE;
459 
460   infodir = PKG_VAR_STATE+"info/";
461   instdir = PKG_VAR_STATE+"installed/";
462 
463   infoname = get_avail_pkg();
464   if (numberof(infoname)==0) error,"No info file found in "+infodir;
465 
466   instname = get_inst_pkg();
467   if (instname==0) instname="";
468 
469   instnameo=[];
470   for (in=1;in<=numberof(pkg_other_installed);in++) {
471     junk=get_inst_pkg(pkg_other_installed(in));
472     if (junk!=0 & !is_void(junk)) {
473       grow,instnameo,junk;
474       grow,instdiro,array(pkg_other_installed(in),numberof(junk));
475     }
476   }
477 
478   text = [];
479 
480   for (i=1;i<=numberof(infoname);i++) {
481     // for all info file, parse file:
482     pkg = parse_info_file(infodir+infoname(i)+".info");
483 
484     is_inst = " ";
485     instvers = "-";
486 
487     // is this package installed locally?
488     w = where(instname==infoname(i));
489     // is yes, what version?
490     if (numberof(w)) {
491       is_inst = "i";
492       instvers = (parse_info_file(instdir+instname(w(1))+".info")).vers;
493       if (instvers<pkg.vers) is_inst="u"; // upgradable (2007dec26)
494     }
495 
496     // is pkg installed in another place?
497     w = where(instnameo==infoname(i));
498     if (numberof(w)) {
499       is_inst += "o";
500       if (instvers=="-") {
501         instvers = (parse_info_file(instdiro(w(1))+instnameo(w(1))+".info")).vers;
502       }
503     }
504 
505     if (am_subroutine()) {
506       write,format="%-2s %-12s %-8s %-8s %-45s\n", is_inst,infoname(i),\
507       pkg.vers,instvers,pkg.desc;
508     } else {
509       grow,text,swrite(format="%-2s %-12s %-8s %-8s %-45s\n", is_inst,infoname(i),\
510       pkg.vers,instvers,pkg.desc);
511     }
512   }
513   return text;
514 }
515 
516 
517 
pkg_info(pkgname)518 func pkg_info(pkgname)
519 /* DOCUMENT pkg_info,pkgname
520    Prints out more information about package "pkgname" (string).
521    Does nothing else.
522    SEE ALSO:
523  */
524 {
525   if (!setup_done) pkg_setup,first=1;
526   if (!PKG_SYNC_DONE) pkg_sync;
527 
528   infodir = PKG_VAR_STATE+"info/";
529 
530   pkg = parse_info_file(infodir+pkgname+".info");
531 
532   write,format="Package    : %s\n",pkg.name;
533   write,format="Version    : %s\n",pkg.vers;
534   write,format="Maintainer : %s\n",pkg.maintainer;
535   write,format="Depends    : %s\n",pkg.depends;
536   write,format="Homepage   : %s\n",pkg.homepage;
537   write,format="Description: \n%s\n",pkg.desc_details;
538   write,format="\n%s\n","\"pkg_list\" to see installed status";
539 }
540 
541 func pkg_setup(first=)
542 /* DOCUMENT pkg_setup
543    Simply print out the global variables relative to this
544    package manager.
545    SEE ALSO: pkg_mngr, pkg_save.
546  */
547 {
548   extern setup_done, user_setup_done;
549   extern pkg_other_installed;
550   extern PKG_Y_HOME, PKG_Y_SITE, PKG_TMP_DIR, PKG_VAR_STATE, PKG_OTHER_INSTALLED;
551   extern PKG_SYNC_DONE;
552   if (first) {
553     write,format="%s\n\n",
554     "pkg_mngr was not setup! You need to go through this set-up once";
555   }
556   write,format="%s\n","Enter set-up parameters for pkg_mngr ([] = default)";
557   write,format="%s\n","The default values should be ok, so if you don't know";
558   write,format="%s\n","what to enter, just press return.";
559 
560   if (!PKG_Y_HOME) PKG_Y_HOME = Y_HOME;
561   if (!PKG_Y_SITE) PKG_Y_SITE = Y_SITE;
562 
563   // refine a bit the default for PKG_Y_HOME in case user setup has not been
564   // done, i.e. user is not superuser.
565   if (!user_setup_done) {
566     if (!has_write_permissions(PKG_Y_HOME)) {
567       if (!is_void(PKG_VAR_STATE))
568         pkg_other_installed=grow([PKG_VAR_STATE+"installed/"],pkg_other_installed);
569       PKG_OTHER_INSTALLED=pathform(pkg_other_installed);
570       PKG_Y_HOME = Y_USER;
571       PKG_VAR_STATE = PKG_Y_HOME+"packages/";
572     }
573     if (!has_write_permissions(PKG_Y_SITE)) PKG_Y_SITE=PKG_Y_HOME;
574     if (!PKG_OTHER_INSTALLED) PKG_OTHER_INSTALLED = "";
575     pkg_other_installed=pathsplit(PKG_OTHER_INSTALLED);
576   } // end !user_setup_done
577 
578   if (!PKG_VAR_STATE) PKG_VAR_STATE = PKG_Y_HOME+"packages/";
579   if (!PKG_OS) PKG_OS=get_env("OSTYPE")+"-"+get_env("MACHTYPE");
580   if (!PKG_FETCH_CMD) PKG_FETCH_CMD="curl -s";
581   if (!PKG_GUNTAR_CMD) PKG_GUNTAR_CMD="tar zxf";
582   if (!PKG_SERVER) PKG_SERVER="http://www.maumae.net/yorick/packages/";
583   if (!PKG_TMP_DIR) PKG_TMP_DIR=PKG_VAR_STATE+"tmp/";
584   if (PKG_VERBOSE==[]) PKG_VERBOSE=1;
585   if (PKG_ASK_CONFIRM==[]) PKG_ASK_CONFIRM=1;
586   if (PKG_RUN_CHECK==[]) PKG_RUN_CHECK=0;
587 
588 
589   write,format="\n%s\n","System command syntax to fetch URL?";
590   PKG_FETCH_CMD = strtrim(kinput("PKG_FETCH_CMD",PKG_FETCH_CMD));
591   write,format="\n%s\n","Where to retrieve binary packages?";
592   PKG_SERVER = strtrim(kinput("PKG_SERVER",PKG_SERVER));
593 
594   // 2007dec26: modify order to fetch possible OSes/architectures
595   oses = pkg_probe_oses(verbose=verbose);
596   write,format="\n%s\n","What is your OS-machine type?";
597   for (i=1;i<=numberof(oses);i++) write,format="  (%d) %s\n",i,oses(i);
598   osn=0;
599   while ((osn<1)|(osn>numberof(oses))) read,prompt="PKG_OS (enter a number): ",osn;
600   PKG_OS = oses(osn);
601 
602   write,format="\n%s\n","System command syntax to untar the package tarballs?";
603   PKG_GUNTAR_CMD = strtrim(kinput("PKG_GUNTAR_CMD",PKG_GUNTAR_CMD));
604   //  PKG_TMP_DIR = kinput("PKG_TMP_DIR",PKG_TMP_DIR);
605   write,format="\n%s\n","Verbose level for pkg_mngr commands (more=more chatty)?";
606   PKG_VERBOSE = kinput("PKG_VERBOSE (0|1|2|3)",PKG_VERBOSE);
607   write,format="\n%s\n","Ask confirmation before installing packages?";
608   PKG_ASK_CONFIRM = kinput("PKG_ASK_CONFIRM (0|1)",PKG_ASK_CONFIRM);
609   write,format="\n%s\n","Run package check.i when installing?";
610   PKG_RUN_CHECK = kinput("PKG_RUN_CHECK",PKG_RUN_CHECK);
611 
612   // TP: my new variables
613   old_pkg_home = PKG_Y_HOME;
614   old_var=PKG_VAR_STATE;
615 
616   write,format="\n%s\n",
617     "Path to install architecture-dependent files?";
618   PKG_Y_HOME = strtrim(kinput("PKG_Y_HOME",PKG_Y_HOME));
619   if (strpart(PKG_Y_HOME,0:0)!="/") PKG_Y_HOME+="/";
620 
621   changing_root=(PKG_Y_HOME!=old_pkg_home);
622   if (changing_root) {
623     // PKG_Y_HOME has changed. Use the new value to define defaults for the
624     // rest of our variabes.
625     if (PKG_Y_HOME==Y_HOME) PKG_Y_SITE=Y_SITE; else PKG_Y_SITE=PKG_Y_HOME;
626     if (PKG_VAR_STATE==old_pkg_home+"packages/")
627       PKG_VAR_STATE=PKG_Y_HOME+"packages/";
628     if (PKG_SETUP==old_pkg_home+"packages/pkg_setup.i")
629       PKG_SETUP=PKG_Y_HOME+"packages/pkg_setup.i";
630   }
631 
632   write,format="\n%s\n%s\n",
633     "Path to install architecture-independent files?",
634     "It is generally advisable to use PKG_Y_SITE=PKG_Y_HOME.";
635   PKG_Y_SITE = strtrim(kinput("PKG_Y_SITE",PKG_Y_SITE));
636   if (strpart(PKG_Y_SITE,0:0)!="/") PKG_Y_SITE+="/";
637 
638   write,format="\n%s\n",
639     "Where should pkg_mngr store its data?";
640   PKG_VAR_STATE = strtrim(kinput("PKG_VAR_STATE",PKG_VAR_STATE));
641   if (strpart(PKG_VAR_STATE,0:0)!="/") PKG_VAR_STATE+="/";
642 
643   write,format="\n%s\n",
644     "Where are other package \"installed\" directories on this system?";
645   write,format="%s\n",
646     "Space for empty string. If you don't know what this is, leave it alone.";
647   if (!PKG_OTHER_INSTALLED | changing_root | PKG_OTHER_INSTALLED=="") {
648     if (!is_void(PKG_OTHER_INSTALLED) & PKG_OTHER_INSTALLED!="")
649       write,format="%s%s\n",
650         "Old value of PKG_OTHER_INSTALLED: ",PKG_OTHER_INSTALLED;
651     if (!is_void(Y_HOMES)) {
652       w = where(Y_HOMES==PKG_Y_HOME);
653       if (numberof(w)==0) w=0; else w=w(1);
654       if (w<numberof(Y_HOMES))
655         pkg_other_installed = Y_HOMES(w+1:)+"packages/installed/";
656     }
657     if (PKG_VAR_STATE!=Y_HOME+"packages/")
658       grow,pkg_other_installed,Y_HOME+"packages/installed/";
659     if (pkg_other_installed!=[]) pkg_other_installed =
660       pkg_other_installed(where(pkg_other_installed!=""));
661     PKG_OTHER_INSTALLED = pathform(pkg_other_installed);
662   }
663   PKG_OTHER_INSTALLED = strtrim(kinput("PKG_OTHER_INSTALLED",PKG_OTHER_INSTALLED));
664 
665   if (strlen(PKG_OTHER_INSTALLED)>0) {
666     pkg_other_installed = pathsplit(PKG_OTHER_INSTALLED);
667     w = where(!strglob("*/",pkg_other_installed)&(pkg_other_installed!=""));
668     if (numberof(w)) {
669       pkg_other_installed(w)+="/";
670       PKG_OTHER_INSTALLED = pathform(pkg_other_installed);
671     }
672   }
673 
674   if (PKG_SETUP==Y_HOME+"packages/pkg_setup.i" |
675       !has_write_permissions(dirname(PKG_SETUP)))
676     PKG_SETUP=PKG_VAR_STATE+"pkg_setup.i";
677   write,format="\n%s\n",
678     "Where should this information be stored?";
679   PKG_SETUP = kinput("PKG_SETUP",PKG_SETUP);
680   while (!strglob("*[^/].i",PKG_SETUP)) {
681     write,format="%s\n",
682       "Please enter a filename ending in .i";
683     PKG_SETUP = kinput("PKG_SETUP",PKG_SETUP);
684   }
685   // end TP
686 
687   write,format="\nPKG_OS = \"%s\"\n",PKG_OS;
688   write,format="PKG_FETCH_CMD = \"%s\"\n",PKG_FETCH_CMD;
689   write,format="PKG_SERVER = \"%s\"\n",PKG_SERVER;
690   write,format="PKG_GUNTAR_CMD = \"%s\"\n",PKG_GUNTAR_CMD;
691   //  write,format="PKG_TMP_DIR = %s\n",PKG_TMP_DIR;
692   write,format="PKG_VERBOSE = %d\n",PKG_VERBOSE;
693   write,format="PKG_ASK_CONFIRM = %d\n",PKG_ASK_CONFIRM;
694   write,format="PKG_RUN_CHECK = %d\n",PKG_RUN_CHECK;
695   write,format="PKG_Y_HOME = \"%s\";\n",PKG_Y_HOME;
696   write,format="PKG_Y_SITE = \"%s\";\n",PKG_Y_SITE;
697   write,format="PKG_VAR_STATE = \"%s\";\n",PKG_VAR_STATE;
698   write,format="PKG_OTHER_INSTALLED = \"%s\";\n",PKG_OTHER_INSTALLED;
699 
700   if (PKG_VAR_STATE!=old_var) {
701   }
702 
703   PKG_SYNC_DONE=[];
704   PKG_TMP_DIR=PKG_VAR_STATE+"tmp/";
705 
706   setup_done = 1;
707   user_setup_done = 1;
708   pkg_save;
709 
710   // inform the user about paths
711 
712   if (PKG_Y_HOME != Y_HOME | PKG_Y_SITE != Y_SITE) {
713   write,format="%s\n", "\n"+
714     "You need to make sure the various Yorick paths will take the relevant\n"+
715     "sub-directories of PKG_Y_HOME and PKG_Y_SITE into account, and that future\n"+
716     "runs of pkg_mngr will know where to find PKG_SETUP.\n"+
717     "\n"+
718     "This is easily done by putting the following lines in any startup file,\n"+
719     "e.g. any .i file in Y_HOME/i-start/ or ~/.yorick/i-start/:\n";
720   write,format=" require,\"pathfun.i\";\n"+" PKG_SETUP=\"%s\";\n",PKG_SETUP;
721   if (PKG_Y_HOME==PKG_Y_SITE)
722     write,format=" add_y_home, \"%s\";\n\n",PKG_Y_HOME;
723   else
724     write,format=" add_y_home, \"%s\", \"%s\";\n\n",PKG_Y_HOME,PKG_Y_SITE;
725   write_pkg_setup_start,1;
726   } else if (PKG_SETUP!=Y_HOME+"packages/pkg_setup.i") {
727   write,format="%s\n","\n"+
728     "You need to make sure that future runs of pkg_mngr will know where\n"+
729     "to find PKG_SETUP.\n"+
730     "\n"+
731     "This is easily done by putting the following line in any startup file,\n"+
732     "e.g. any .i file in Y_HOME/i-start/ or ~/.yorick/i-start/:\n";
733   write,format="PKG_SETUP=\"%s\";\n",PKG_SETUP;
734   write_pkg_setup_start,2;
735   }
736   if (PKG_Y_HOME==Y_HOME & PKG_Y_SITE!=Y_SITE) {
737   write,format="%s\n","\n"+
738     "WARNING: you set PKG_Y_HOME==Y_HOME but PKG_Y_SITE!=Y_SITE.\n"+
739     "Expect trouble.\n"+
740     "I strongly advise you to rerun pkg_setup and set PKG_Y_SITE\n"+
741     "and PKG_Y_HOME to sane values.\n\n";
742   } else if (PKG_Y_HOME!=Y_HOME & PKG_Y_SITE==Y_SITE) {
743   write,format="%s\n","\n"+
744     "WARNING: you set PKG_Y_HOME!=Y_HOME but PKG_Y_SITE==Y_SITE.\n"+
745     "Expect trouble.\n"+
746     "I strongly advise you to rerun pkg_setup and set PKG_Y_SITE\n"+
747     "and PKG_Y_HOME to sane values.\n\n";
748   }
749 }
750 
751 func pkg_upgrade(pkgnames,verbose=)
752 /* DOCUMENT pkg_upgrade,pkgnames,verbose=
753    Upgrade requested packages, or all upgradable packages if no arguments
754    Upgrade to highest available version.
755 
756    pkg_upgrade,packages
757    pkgname can be a string scalar, vector, and contains wildcard to
758    install multiple packages in one call.
759 
760    examples: pkg_upgrade,"y*" or pkg_upgrade,["soy","yao"] or pkg_upgrade,"*"
761 
762    Without arguments, upgrade all upgradable packages.
763 
764    bug: I don't correctly parse the version, so 0.5.03 is considered
765    lower than 0.5.2
766    SEE ALSO:
767  */
768 {
769   if (is_void(pkgnames)) {
770     pkgnames="*";
771     askall=1;
772   }
773 
774   instpkg = get_avail_pkg();
775   allpkg = [];
776   for (np=1;np<=numberof(pkgnames);np++) {
777     w = where(strglob(pkgnames(np),instpkg));
778     if (numberof(w)==0) {
779       write,format="WARNING: No such package \"%s\"\n",pkgnames(np);
780       continue;
781     }
782     grow,allpkg,instpkg(w);
783   }
784   pkgnames = allpkg;
785   if (numberof(pkgnames)==0) return 0;
786 
787 
788   li = pkg_list();
789   upgradable = (strpart(li,1:1)=="u");
790   pname = strtrim(strpart(li,3:16));
791   uvers = strtrim(strpart(li,17:24));
792   ivers = strtrim(strpart(li,26:32));
793   ww=[];
794 
795   for (i=1;i<=numberof(pkgnames);i++) {
796     w = where(pkgnames(i)==pname)(1);
797     if (ivers(w)=="-") continue; // not installed, ignore.
798     if (numberof(w)==0) {
799       write,format="WARNING: No such package \"%s\"\n",pkgnames(i);
800       continue;
801     }
802     if (upgradable(w)) {
803       write,format="Upgrade: %s from version %s to %s\n",
804         pname(w),ivers(w),uvers(w);
805       grow,ww,w;
806     } else {
807       if (!askall) \
808         write,format="Package %s is already at the latest version %s\n",
809           pname(w),ivers(w);
810     }
811   }
812 
813   if (numberof(ww)==0) {
814     write,format="%s\n","No package to upgrade";
815     return 0;
816   }
817 
818   if (PKG_ASK_CONFIRM) {
819     ans = strtolower(strtrim(kinput("OK","y")));
820     if (ans!="y") {
821       write,format="%s\n","Aborting";
822       return;
823     }
824   }
825 
826   for (i=1;i<=numberof(ww);i++) {
827     pkg_install,pname(ww(i)),noconfirm=1,verbose=verbose,_version=uvers(ww(i));
828   }
829 }
830 
831 
832 func pkg_install(pkgnames,verbose=,check=,force=,noconfirm=,_recur=,_version=,_vrel=)
833 /* DOCUMENT pkg_install,pkgname,force=,check=,verbose=
834    Install package "pkgname" (string vector)
835    Grabs the tarball from a central server, untar it, copy the files
836    according to the directory structure specified in the untared
837    package, possibly run a preflight and postflight include files (for
838    special needs), and place the package info file and a list of
839    installed files in package/installed/.
840 
841    pkgname can be a string scalar, vector, and contains wildcard to
842    install multiple packages in one call.
843    examples: pkg_install,"y*" or pkg_install,["soy","yao"] or pkg_install,"*"
844 
845    Keywords:
846    force: force installation
847    check: run checks after install
848    verbose: 0 (silent), 1 (some messages), 2 (chatty), 3 (more chatty)
849 
850    SEE ALSO: pkg_mngr, pkg_remove.
851  */
852 {
853   extern _pkg_recur_tree; // to avoid recursion
854   if (!(setup_done & user_setup_done)) pkg_setup,first=1;
855   if (!PKG_SYNC_DONE) pkg_sync;
856 
857   if (!_recur) { // first call in possible recursion
858     // the following is to expand the possible wildcards
859     // in elements of the vector pkgnames
860     // e.g. a call can be pkg_install,["y*","soy"]
861     instpkg = get_avail_pkg();
862     allpkg = [];
863     for (np=1;np<=numberof(pkgnames);np++) {
864       w = where(strglob(pkgnames(np),instpkg));
865       if (numberof(w)==0) {
866         write,format="WARNING: No such package \"%s\"\n",pkgnames(np);
867         continue;
868       }
869       grow,allpkg,instpkg(w);
870     }
871 
872     pkgnames = allpkg;
873     if (numberof(pkgnames)==0) return 0;
874 
875     if ((PKG_ASK_CONFIRM)&(noconfirm!=1)) {
876       if (force) write,format="%s\n","Packages to (re)install :";
877       else write,format="%s\n","Packages to install :";
878       write,pkgnames;
879       // figure out dependencies (ignore version for now)
880       depjunk=[];
881       infodir = PKG_VAR_STATE+"info/";
882       instdir = PKG_VAR_STATE+"installed/";
883       for (i=1;i<=numberof(pkgnames);i++) {
884         ins=parse_info_file(infodir+pkgnames(i)+".info");
885         tmp=ins.depends_pkg;
886         tmp=tmp(where(tmp));
887         grow,depjunk,tmp;
888       }
889       if (depjunk!=[]) {
890         // already installed packages:
891         ins = lsdir(instdir);
892         instname = "yorick";
893         if (ins!=[]) {
894           w = strgrep("([a-zA-Z0-9\_\.\+\-]*)(.info$)",ins,sub=[1]);
895           tmp = strpart(ins,w);
896           grow,instname,tmp(where(tmp));
897         }
898         // sort dependencies in alphabetical order
899         tmp=depjunk(sort(depjunk));
900         depjunk=[];
901         // get rid of yorick and double entries
902         for (i=1;i<=numberof(tmp);i++) {
903           if (tmp(i)=="yorick") continue;
904           if ((anyof(strmatch(instname,tmp(i))))&(!force)) continue; // already installed
905           if (depjunk==[]) grow,depjunk,tmp(i);
906           else if (tmp(i)!=depjunk(0)) grow,depjunk,tmp(i);
907         }
908         if (depjunk!=[]) {
909           if (force) write,format="%s\n","Dependencies to be re-installed :";
910           else write,format="%s\n","Dependencies to be installed :";
911           write,depjunk;
912         }
913       }
914       if (kinput("OK? ","y")!="y") return 0;
915     }
916   }
917 
918   for (np=1;np<=numberof(pkgnames);np++) {
919 
920     pkgname = pkgnames(np);
921 
922     if (!setup_done) pkg_setup,first=1;
923 
924     if (!_recur)      _pkg_recur_tree=[];
925     if (verbose==[])  verbose=PKG_VERBOSE;
926     if (_version==[]) _version="0.0";
927     if (_vrel==[])    _vrel=">=";
928     if (check==[])    check=PKG_RUN_CHECK;
929     if (pkgname==[])  error,"Must specify a string-type package name";
930 
931     infodir = PKG_VAR_STATE+"info/";
932     instdir = PKG_VAR_STATE+"installed/";
933     tarbdir = PKG_VAR_STATE+"tarballs/";
934 
935     if (anyof(_pkg_recur_tree) && anyof(strmatch(_pkg_recur_tree,pkgname)))
936       //we just installed it, to avoid recursing, we should exit.
937       continue;
938 
939     // is this package already installed with a version > requested version ?
940     ins = lsdir(instdir);
941     if (!force && anyof(ins)) {
942       w = strgrep("([a-zA-Z0-9\_\.\+\-]*)(.info$)",ins,sub=[1]);
943       instname = strpart(ins,w);
944       w = where(instname==pkgname);
945       if (numberof(w)!=0) {
946         // this package has been installed. check version:
947         ins=parse_info_file(instdir+pkgname+".info");
948         if (vers_cmp(ins.vers,_vrel,_version)) {
949           if (verbose && !_recur) {
950             write,format="Package %s already installed (%s, needed %s)\n", \
951               pkgname,ins.vers,_version;
952           }
953           continue;
954         }
955       }
956     }
957     // TP: repeat the same in each PKG_OTHER_INSTALLED directory.
958     // Rationale: don't reinstall dependencies that are already installed at
959     // the system level.
960     installed_elsewhere=0;
961     for (in=1;in<=numberof(pkg_other_installed);in++) {
962       ins = lsdir(pkg_other_installed(in));
963       if (!force && anyof(ins)) {
964         w = strgrep("([a-zA-Z0-9\_\.\+\-]*)(.info$)",ins,sub=[1]);
965         instname = strpart(ins,w);
966         w = where(instname==pkgname);
967         if (numberof(w)!=0) {
968           // this package has been installed. check version:
969           ins=parse_info_file(pkg_other_installed(in)+pkgname+".info");
970           if (vers_cmp(ins.vers,_vrel,_version)) {
971             if (verbose && !_recur) {
972               write,format="Package %s already installed in %s (%s, needed %s)\n", \
973                 pkgname,pkg_other_installed(in),ins.vers,_version;
974             }
975             installed_elsewhere=1;
976           }
977           // don't go further: assume this version is the one that will be
978           // used, we don't want to know whether the right one is installed
979           // somewhere where it will be ignored.
980           break;
981         }
982       }
983     }
984     if (installed_elsewhere) continue;
985     // end TP
986 
987     // upgrade: here, check if last version of package already installed
988 
989     pkg=parse_info_file(infodir+pkgname+".info");
990 
991     // works out dependencies:
992     for (i=1;i<=numberof(where(pkg.depends_pkg));i++) {
993 
994       // yorick version number:
995       if (pkg.depends_pkg(i)=="yorick") {
996         if (!vers_cmp(Y_VERSION,pkg.depends_rel(i),pkg.depends_vers))   \
997           error,"This package needs yorick version "+
998             pkg.depends_rel(i)+" "+pkg.depends_vers(i);
999         continue;
1000       }
1001 
1002       // here work out the possible recursion. use _pkg_uptree
1003       // other dependencies:
1004       pkg_install,pkg.depends_pkg(i),verbose=verbose,force=force,
1005         _recur=1,_version=pkg.depends_vers(i),_vrel=pkg.depends_rel(i);
1006 
1007       // add the name to tree to avoid recursion
1008       grow,_pkg_recur_tree,pkg.depends_pkg(i);
1009     }
1010 
1011     pkgtgz = strtok(pkg.source,"/",10);
1012     pkgtgz = pkgtgz(where(pkgtgz))(0);
1013 
1014     cd,tarbdir;
1015 
1016     // fetch the tarball
1017     localtarballs = lsdir(".");
1018     if ((noneof(localtarballs))|| // no tarballs OR
1019         (noneof(strmatch(localtarballs,pkgtgz)))|| //no match in loc. tarballs
1020         (force) ) { // we were asked to force the install anyway
1021       // this package does not exist locally.
1022       if (verbose==1) {
1023         write,format="%-20s: %s",pkgname,"Fetching tarball..";
1024       } else if (verbose>=2) {
1025         write,format="%s\n","--------------------------";
1026         write,format="%s: %s\n",pkgname,"Fetching tarball";
1027       }
1028       pkg_fetch_url,pkg.source,tarbdir+pkgtgz,verbose=verbose;
1029     } else {
1030       if (verbose==1) {
1031         write,format="%-20s: %s",pkgname,"Using local tarball..";
1032       } else if (verbose>=2) {
1033         write,format="%s\n","--------------------------";
1034         write,format="%s: %s\n",pkgname,"Using local tarball";
1035       }
1036       // the tarball exist locally, we'll use it (unless force=1)
1037     }
1038 
1039     // gunzip and untar:
1040     if (verbose==1) {
1041       write,format="%s","Inflating..";
1042     } else if (verbose>=2) {
1043       write,format="%s: %s\n",pkgname,"Inflating tarball";
1044     }
1045     pkg_sys,PKG_GUNTAR_CMD+" "+tarbdir+pkgtgz,verbose=verbose;
1046 
1047     // run preflight.i
1048     if (open(pkgname+"/preflight.i","r",1)) {
1049       if (verbose) write,format="\n%s\n","Running preflight.i";
1050       include,pkgname+"/preflight.i",1;
1051     }
1052 
1053     list1=list2=[];
1054 
1055     // copy to Y_SITE:
1056     if (anyof(lsdir(pkgname+"/dist/y_site/"))) {
1057       recursive_rename,pkgname+"/dist/y_site",PKG_Y_SITE,
1058         list1,verbose=verbose,init=1;
1059     }
1060 
1061     // copy to Y_HOME:
1062     if (anyof(lsdir(pkgname+"/dist/y_home/"))) {
1063       recursive_rename,pkgname+"/dist/y_home",PKG_Y_HOME,
1064         list2,verbose=verbose,init=1;
1065     }
1066 
1067     list = _(list1,list2);
1068 
1069     if (verbose>=2) {
1070       write,format="%s\n","Installed files:";
1071       write,format="  + %s\n",list;
1072     }
1073 
1074     // copy info file:
1075     rename,pkgname+"/"+pkgname+".info",instdir+"/"+pkgname+".info";
1076     if (verbose>=3) write,format="Executing rename,%s,%s\n",
1077       pkgname+"/"+pkgname+".info",instdir+"/"+pkgname+".info";
1078 
1079     f = open(instdir+"/"+pkgname+".flist","w");
1080     write,f,format="%s\n",list;
1081     close,f;
1082 
1083     if (open(pkgname+"/postflight.i","r",1)) {
1084       if (verbose) write,format="%s\n","Running postflight.i";
1085       include,pkgname+"/postflight.i",1;
1086     }
1087 
1088     //run check if requested and pkgname/check.i file present
1089     if (check && open(pkgname+"/check.i","r",1)) {
1090       if (verbose==1) {
1091         write,format="%s","Checking..";
1092       } else if (verbose>=2) {
1093         write,format="%s\n","Checking package";
1094       }
1095       cd,pkgname;
1096       if (anyof(pkgname==["imutil","curses","yorz","yutils","yao"])) {
1097         include,"check.i";
1098       } else {
1099         include,"check.i",1;
1100       }
1101     }
1102 
1103     // clean up after ourselves
1104     recursive_rmdir,tarbdir+pkgname+"/dist",verbose=verbose;
1105 
1106     // if we got there, it ought to be OK
1107     if (verbose==1) {
1108       write,format="%s","installed\n";
1109     } else if (verbose>=2) {
1110       write,format="%s installed sucessfully\n",pkgname;
1111     }
1112 
1113   }
1114   return 0;
1115 }
1116 
1117 
1118 func pkg_reset(verbose=)
1119 /* DOCUMENT pkg_reset(verbose=)
1120    Brute force solution: if a bad tarball has been downloaded,
1121    pkg_mngr will try and try using it and fail. One can remove
1122    it by hand in Y_HOME/packages/tarballs/ or use this routine
1123    to remove them all.
1124    SEE ALSO:
1125  */
1126 {
1127   if (verbose==[]) verbose=PKG_VERBOSE;
1128 
1129   rep=kinput("Delete all pkg_mngr tarballs and start from scratch","n");
1130   if (rep!="y") return
1131 
1132   tarbdir = PKG_VAR_STATE+"tarballs/";
1133   recursive_rmdir,tarbdir,verbose=verbose;
1134   mkdir,tarbdir;
1135 }
1136 
1137 
1138 func pkg_remove(pkgnames,verbose=)
1139 /* DOCUMENT pkg_remove,pkgname,verbose=
1140    Remove package "pkgname" (string)
1141    Remove all files (libraries, include files, autoload)
1142    that were installed by the installer.
1143 
1144    pkgname can be a string scalar, vector, and contains wildcard to
1145    remove multiple packages in one call.
1146    examples: pkg_remove,"y*" or pkg_remove,["soy","yao"] or pkg_remove,"*"
1147 
1148    help,pkg_mngr for more details.
1149 
1150    SEE ALSO: pkg_mngr, pkg_install
1151  */
1152 {
1153   if (!(setup_done & user_setup_done)) pkg_setup,first=1;
1154   if (verbose==[]) verbose=PKG_VERBOSE;
1155   if (!PKG_SYNC_DONE) pkg_sync;
1156   if (pkgnames==[]) error,"Must specify a string-type package name";
1157 
1158   instpkg = get_inst_pkg();
1159   if (noneof(instpkg)) return;
1160 
1161   allpkg = [];
1162   for (np=1;np<=numberof(pkgnames);np++) {
1163     w = where(strglob(pkgnames(np),instpkg));
1164     if (numberof(w)==0) {
1165       write,format="No package corresponding to %s\n",pkgnames(np);
1166       continue;
1167     }
1168     grow,allpkg,instpkg(w);
1169   }
1170 
1171   pkgnames = allpkg;
1172   if (numberof(pkgnames)==0) return 0;
1173 
1174   if (PKG_ASK_CONFIRM) {
1175     write,format="%s\n","Packages to remove :";
1176     write,pkgnames;
1177     if (kinput("OK? ","y")!="y") return 0;
1178   }
1179 
1180   instdir = PKG_VAR_STATE+"installed/";
1181 
1182   for (np=1;np<=numberof(pkgnames);np++) {
1183     pkgname = pkgnames(np);
1184 
1185     if (verbose==1) {
1186       write,format="%-20s: Removing...",pkgname;
1187     } else if (verbose>=2) {
1188       write,format="%s\n","--------------------------";
1189       write,format="Removing package %s\n",pkgname;
1190     }
1191 
1192     instpkg = get_inst_pkg();
1193     if (noneof(strmatch(instpkg,pkgname))) {
1194       if (verbose) write,format="\nPackage %s is not installed\n",pkgname;
1195       continue;
1196     }
1197 
1198     files = rdfile(instdir+pkgname+".flist");
1199 
1200     for (i=1;i<=numberof(files);i++) {
1201       if (verbose>=2) write,format=" - %s\n",files(i);
1202       remove,files(i);
1203     }
1204 
1205     file = instdir+pkgname+".info";
1206     if (verbose>=2) write,format=" - %s\n",file;
1207     remove,file;
1208 
1209     file = instdir+pkgname+".flist";
1210     if (verbose>=2) write,format=" - %s\n",file;
1211     remove,file;
1212 
1213     // if we got there, we ought to be OK
1214     if (verbose==1) {
1215       write,format="%s\n","done";
1216     } else if (verbose>=2) {
1217       write,format="%s removed successfully\n",pkgname;
1218     }
1219   }
1220   return 0;
1221 }
1222 
1223 
1224 /********************************************\
1225  *           UTILITARY FUNCTIONS            *
1226 \********************************************/
1227 
get_avail_pkg(void)1228 func get_avail_pkg(void)
1229 {
1230   infodir = PKG_VAR_STATE+"info/";
1231 
1232   all = lsdir(infodir);
1233   if (anyof(all)) {
1234     w = strgrep("([a-zA-Z0-9\_\.\+\-]*)(.info$)",all,sub=[1]);
1235     infoname = strpart(all,w);
1236     infoname = infoname(where(infoname));
1237   }
1238   return infoname;
1239 }
1240 
get_inst_pkg(instdir)1241 func get_inst_pkg(instdir)
1242 {
1243   if (!instdir) instdir = PKG_VAR_STATE+"installed/";
1244 
1245   all = lsdir(instdir);
1246   if (anyof(all)) {
1247     w = strgrep("([a-zA-Z0-9\_\.\+\-]*)(.info$)",all,sub=[1]);
1248     instname = strpart(all,w);
1249     instname = instname(where(instname));
1250   }
1251   return instname;
1252 }
1253 
1254 func recursive_rmdir(dir,verbose=)
1255 /* DOCUMENT recursive_rmdir,dir,verbose=
1256    Recursively delete all files and directories under dir, dir
1257    included.
1258    Equivalent to the linux/unix command
1259    rm -rf dir
1260    Note that recursive_rmdir,"." will fail removing "." (for
1261    some reason).
1262    SEE ALSO:
1263  */
1264 {
1265   if (strpart(dir,0:0)!="/") dir+="/";
1266   if (verbose==[]) verbose=PKG_VERBOSE;
1267 
1268   if (allof(lsdir(dir)==0)) return 0;
1269 
1270   orig_dir = dir;
1271 
1272   do {
1273 
1274     f = lsdir(dir,subdirs);
1275 
1276     if ( (noneof(f)) && (noneof(subdirs)) ) {
1277       // no files, no subdirs, ok to remove dir:
1278       if (verbose>=3) write,format="Removing directory %s\n",dir;
1279       rmdir,dir;
1280       if (lsdir(dir)!=0) error,"Can't remove directory "+dir;
1281       // and exit (cul-de-sac):
1282       break;
1283     }
1284 
1285     if (anyof(f)) {
1286       // some files in here, remove them
1287       for (i=1;i<=numberof(f);i++) {
1288         if (verbose>=3) write,format="Removing %s\n",dir+f(i);
1289         remove,dir+f(i);
1290       }
1291     }
1292 
1293     // no more files. If no subdirs, remove dir and exit:
1294     if (noneof(subdirs)) {
1295       if (verbose>=3) write,format="Removing directory %s\n",dir;
1296       rmdir,dir;
1297       break;
1298     } else {
1299       // else go down one level
1300       dir += subdirs(1)+"/";
1301     }
1302   } while (1);
1303 
1304   recursive_rmdir,orig_dir,verbose=verbose;
1305 }
1306 
1307 
1308 
1309 func recursive_rename(dir1,dir2,&list,verbose=,init=)
1310 /* DOCUMENT recursive_rename,dir1,dir2,&list,verbose=,init=
1311    As it says. This routine will move the whole content of
1312    dir1 to dir2, recursively.
1313    Absolute equivalent to the linux/unix command
1314    cp -pr dir1 dir1
1315    Except that the files get moved, not copied.
1316    SEE ALSO:
1317  */
1318 {
1319   local sub1;
1320 
1321   if (init) list=[];
1322   if (verbose==[]) verbose=PKG_VERBOSE;
1323 
1324   if (verbose>=3) write,format="Recursive rename %s to %s\n",dir1,dir2;
1325 
1326   // make sure dir ends by slash
1327   if (strpart(dir1,0:0)!="/") dir1+="/";
1328   if (strpart(dir2,0:0)!="/") dir2+="/";
1329 
1330   // get files and subdirs
1331   f1 = lsdir(dir1,sub1);
1332 
1333   // make sure destination dir exists:
1334   f2 = lsdir(dir2);
1335 
1336   // if not, create:
1337   if (allof(f2)==0) {
1338     mkdirp,dir2;
1339     if (lsdir(dir2)==0) error,"Creating "+dir2+" failed (check permission)";
1340     if (verbose>=2) write,format="%s\n","Created "+dir2;
1341   }
1342 
1343   // finally, move the files:
1344   for (i=1;i<=numberof(f1);i++) {
1345     if (verbose>=3)
1346       write,format="Executing rename,%s,%s\n",dir1+f1(i),dir2+f1(i);
1347     rename,dir1+f1(i),dir2+f1(i);
1348     grow,list,dir2+f1(i);
1349   }
1350 
1351   for (i=1;i<=numberof(sub1);i++) {
1352     recursive_rename,dir1+sub1(i),dir2+sub1(i),list,verbose=verbose;
1353   }
1354 }
1355 
1356 
1357 
vers_cmp(v1,op,v2)1358 func vers_cmp(v1,op,v2)
1359 /* DOCUMENT vers_cmp(v1,op,v2)
1360    Compare (software) version, using operator op.
1361    Returns 0 (fail) or 1 (pass).
1362    Operators allowed are >=, >, ==, < and <=
1363    Will possibly fails for subversion # > 99 (will exit in error if
1364    case arises)
1365    SEE ALSO:
1366  */
1367 {
1368 
1369   ndown=5;
1370 
1371   tok1 = strtok(v1,".",ndown);
1372   tok2 = strtok(v2,".",ndown);
1373 
1374   n1=n2=0l;
1375 
1376   for (i=1;i<=ndown;i++) {
1377     n=0;
1378     sread,tok1(i),n;
1379     if (n>99) error,
1380       swrite(format="n>99 (%d): possible failure mode!",n);
1381     n1+=n*100^(ndown-i); //limits version numbering to *.99.*
1382   }
1383 
1384   for (i=1;i<=ndown;i++) {
1385     n=0;
1386     sread,tok2(i),n;
1387     if (n>99) error,
1388       swrite(format="n>99 (%d): possible failure mode!",n);
1389     n2+=n*100^(ndown-i); //limits version numbering to *.99.*
1390   }
1391 
1392   if (op==">=") if (n1>=n2) return 1;
1393   if (op==">")  if (n1>n2) return 1;
1394   if (op=="<=") if (n1<=n2) return 1;
1395   if (op=="<")  if (n1<n2) return 1;
1396   if (op=="==") if (n1==n2) return 1;
1397 
1398   return 0;
1399 }
1400 
1401 
1402 
parse_info_file(file)1403 func parse_info_file(file)
1404 /* DOCUMENT parse_info_file(file)
1405    Parse a .info file (file describing a package in the
1406    yorick package manager, similar to fink/debian info files)
1407    Return a structure with elements = parsed keywords values
1408    SEE ALSO:
1409  */
1410 {
1411   if (!open(file,"r",1)) error,"Can not find (package exist?)"+file;
1412 
1413   text = rdfile(file);
1414 
1415   pkg = pkginfo_str();
1416 
1417   pkg.version -=99;
1418 
1419   pkg.name = pkg_get_keyword_value(text,"Package");
1420   pkg.kind = pkg_get_keyword_value(text,"Kind");
1421   pkg.vers = pkg_get_keyword_value(text,"Version");
1422   pkg.revision = pkg_get_keyword_value(text,"Revision");
1423   pkg.desc = pkg_get_keyword_value(text,"Description");
1424   pkg.license = pkg_get_keyword_value(text,"License");
1425   pkg.maintainer = pkg_get_keyword_value(text,"Maintainer");
1426   pkg.os = pkg_get_keyword_value(text,"OS");
1427   pkg.depends = pkg_get_keyword_value(text,"Depends");
1428   pkg.source = pkg_get_keyword_value(text,"Source");
1429   pkg.md5 = pkg_get_keyword_value(text,"Source-MD5");
1430   pkg.dir = pkg_get_keyword_value(text,"Source-Directory");
1431   pkg.docs = pkg_get_keyword_value(text,"DocFiles");
1432   pkg.homepage = pkg_get_keyword_value(text,"Homepage");
1433   pkg.desc_details = pkg_get_keyword_value(text,"DescDetail");
1434 
1435 
1436   // deals with source
1437   pkg.source = streplace(pkg.source,strgrep("%v",pkg.source),pkg.vers);
1438   pkg.source = streplace(pkg.source,strgrep("%v",pkg.source),pkg.vers);
1439   pkg.source = streplace(pkg.source,strgrep("%o",pkg.source),pkg.os);
1440   pkg.source = streplace(pkg.source,strgrep("%o",pkg.source),pkg.os);
1441 
1442   // parse version
1443   i=1; tmp = pkg.vers;
1444   do {
1445     tmp = strtok(tmp,".");
1446     sread,tmp(1),format="%d",pkg.version(i);
1447     tmp = tmp(2);
1448     i++;
1449   } while (tmp);
1450 
1451   // parse depends
1452   i=1; tmp = pkg.depends;
1453   do {
1454     tmp = strtok(tmp,",");
1455     if (strmatch(tmp(1),"(")) {
1456       tmp2 = strtok(tmp(1),"(");
1457       pkg.depends_pkg(i) = strtrim(tmp2(1));
1458       pkg.depends_vers(i) = strpart(tmp2(2),strgrep("[0-9.]+",tmp2(2)));
1459       pkg.depends_rel(i) = strpart(tmp2(2),strgrep("[><=]+",tmp2(2)));
1460     } else {
1461       pkg.depends_pkg(i) = strtrim(tmp(1));
1462     }
1463     tmp = tmp(2);
1464     i++;
1465   } while (tmp);
1466 
1467 
1468   return pkg;
1469 }
1470 
1471 
1472 
pkg_get_keyword_value(text,keyw)1473 func pkg_get_keyword_value(text,keyw)
1474 /* DOCUMENT pkg_get_keyword_value(text,keyw)
1475    return parsed value of keyword read from a .info
1476    file, in the form:
1477    keyword: value
1478    or
1479    keyword: >>
1480    multi
1481    line value
1482    >>
1483    SEE ALSO:
1484  */
1485 {
1486   w = where(strgrep("^"+keyw,text)(2,)!=-1);
1487   if (numberof(w)==0) {
1488     write,format="%s\n","WARNING: Keyword "+keyw+" does not exist";
1489     return "";
1490   }
1491 
1492   res = strtrim(strtok(text(w(1)),":")(2));
1493 
1494   if (res == "<<") { // multi line keyword
1495     res = "";
1496     w = w(1)+1;
1497     do {
1498       res += text(w)+"\n";
1499       w++;
1500     } while (strtrim(text(w))!="<<");
1501     res = strpart(res,1:-1); //suppress last \n
1502   }
1503 
1504   return res;
1505 }
1506 
1507 
1508 
1509 func pkg_sys(cmd,verbose=)
1510 /* DOCUMENT pkg_sys(cmd,verbose=)
1511    simple interface to the system command.
1512    Allows echoing of commands output to system
1513    SEE ALSO:
1514  */
1515 {
1516   if (verbose==[]) verbose=PKG_VERBOSE;
1517 
1518   if (verbose>=3) write,format="Spawning %s\n",cmd;
1519 
1520   system,cmd;
1521 
1522   return 0;
1523 }
1524 
1525 func pkg_probe_oses(void,verbose=)
1526 {
1527   write,format="%s\n","\nRetrieving OS-ARCH alternatives...please wait";
1528   tmpdir=Y_USER+"packages/tmp/"; // PKG_TMP_DIR not yet defined by user
1529   mkdirp,tmpdir;
1530   pkg_fetch_url,PKG_SERVER,tmpdir+".oses",verbose=verbose;
1531   ctn = rdfile(tmpdir+".oses");
1532   match = strmatch(ctn,"[DIR]");
1533   if (anyof(match)) {
1534     ctn = ctn(where(match));
1535     oses = strpart(ctn,strgrep("([HREF,href]=\\\")([a-zA-Z0-9\_\.\+\-]+)",ctn,sub=2));
1536     oses = oses(where(oses));
1537     oses = oses(where(oses!="tarballs"));
1538     oses = oses(where(oses!="src"));
1539     oses = oses(where(oses!="linux")); // deprecated. now linux-arch
1540     oses = oses(where(oses!="macosx")); // depracated, now darinw-arch
1541   } else error,swrite(format="Did not find any OS directory at %s\n",PKG_SERVER);
1542   return oses;
1543 }
1544 
1545 func pkg_fetch_url(url,dest,verbose=)
1546 /* DOCUMENT pkg_fetch_url(url,dest,verbose=)
1547    wrap the url fetch command (e.g. curl) with error checking
1548    SEE ALSO:
1549  */
1550 {
1551   // fetch the page/file:
1552   pkg_sys,PKG_FETCH_CMD+" "+url+" > "+dest,verbose=verbose;
1553 
1554   if (!(f=open(dest,"r",1))) {
1555     write,"";
1556     error,"No file fetched (connection down?)";
1557   }
1558 
1559   ctn = rdline(f,30);
1560 
1561   if (numberof(where(ctn))==0) {
1562     write,"";
1563     error,"Zero length file (connection down?)";
1564   }
1565 
1566   if (anyof(strmatch(ctn,"<title>Error 404: Page Not Found"))) {
1567     write,"";
1568     error,"Page not found (error 404)";
1569   }
1570 
1571   return 0;
1572 }
1573 
1574 
kinput(prompt,default)1575 func kinput(prompt,default)
1576 {
1577   if (typeof(default)=="string") {
1578     s = swrite(format=prompt+" [\"%s\"]: ",default);
1579     sres = rdline(,1,prompt=s)(1);
1580     if (sres == "") return default;
1581     res = sres;
1582   } else if ((typeof(default)=="long")||(typeof(default)=="int")) {
1583     s = swrite(format=prompt+" [%d]: ",long(default));
1584     sres = rdline(,1,prompt=s)(1);
1585     if (sres == "") return default;
1586     res = 1l;
1587     sread,sres,res;
1588   } else if ((typeof(default)=="double")||(typeof(default)=="float")) {
1589     s = swrite(format=prompt+" [%f]: ",double(default));
1590     sres = rdline(,1,prompt=s)(1);
1591     if (sres == "") return default;
1592     res = 1.0;
1593     sread,sres,res;
1594   } else error,"type not supported";
1595 
1596   return res;
1597 }
1598 
1599 
1600 
write_pkg_setup_start(case)1601 func write_pkg_setup_start(case)
1602 {
1603   rep=kinput("Do you want me to write this in \""+Y_USER+"i-start/00pkg_mngr.i\" [y/n]?","y");
1604   if (rep=="y") {
1605     mkdirp,Y_USER+"i-start";
1606     f = open(Y_USER+"i-start/00pkg_mngr.i","w");
1607     // write,f,format="%s\n",
1608     //  "autoload,\"pkg_mngr.i\",pkg_setup,pkg_list,pkg_sync,pkg_remove,pkg_install;";
1609     if (case==1) write,f,format="%s\n","require,\"pathfun.i\";";
1610     write,f,format="PKG_SETUP=\"%s\";\n",PKG_SETUP;
1611     if (case==1) {
1612       if (PKG_Y_HOME==PKG_Y_SITE)
1613         write,f,format="add_y_home,\"%s\";\n",PKG_Y_HOME;
1614       else
1615         write,f,format="add_y_home,\"%s\",\"%s\";\n",PKG_Y_HOME,PKG_Y_SITE;
1616     }
1617     close,f;
1618     write,format="NOTE: --> Generated \"%s\"\n",Y_USER+"i-start/00pkg_mngr.i";
1619   }
1620 }
1621 
1622