OSDN Git Service

Eliminate invalid comparisons of "this" with nullptr.
[mingw/mingw-get.git] / src / climain.cpp
index 651fae3..9230526 100644 (file)
@@ -4,7 +4,7 @@
  * $Id$
  *
  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
- * Copyright (C) 2009, 2010, MinGW Project
+ * Copyright (C) 2009-2013, MinGW.org Project
  *
  *
  * Implementation of the main program function, which is invoked by
  *
  */
 #include <stdio.h>
+#include <libgen.h>
 #include <string.h>
 #include <fcntl.h>
 
 #include "dmh.h"
+#include "mkpath.h"
 
 #include "pkgbase.h"
 #include "pkgkeys.h"
+#include "pkgopts.h"
 #include "pkgtask.h"
 
-EXTERN_C int climain( int argc, char **argv )
+EXTERN_C void cli_setopts( struct pkgopts *opts )
 {
-  try
-  {
+  /* Start-up hook used to make the table of command line options,
+   * as parsed by the CLI start-up module, available within the DLL.
+   */
+  (void) pkgOptions( OPTION_TABLE_ASSIGN, opts );
+}
 
-  /* Set up the diagnostic message handler, using the console's
-   * `stderr' stream for notifications...
+EXTERN_C pkgOpts *pkgOptions( int action, struct pkgopts *ref )
+{
+  /* Global accessor for the program options data table.
    */
-  dmh_init( DMH_SUBSYSTEM_TTY, *argv++ );
+  static pkgOpts *table = NULL;
+  if( action == OPTION_TABLE_ASSIGN )
+    /*
+     * This is a request to initialise the data table reference;
+     * it is typically called at program start-up, to record the
+     * location of the data table into which the CLI start-up
+     * module stores the result of its CLI options parse.
+     */
+    table = (pkgOpts *)(ref);
 
-  /* TODO: insert code here, to interpret any OPTIONS specified
-   * on the command line.
+  /* In all cases, we return the assigned data table location.
    */
+  return table;
+}
 
-  /* Interpret the `action keyword', specifying the action to be
-   * performed on this invocation...
+class pkgArchiveNameList
+{
+  /* A locally implemented class, managing a LIFO stack of
+   * package names; this is used when processing the source
+   * and licence requests, to track the packages processed,
+   * so that we may avoid inadvertent duplicate processing.
    */
-  int action = action_code( *argv );
-  if( action < 0 )
-    /*
-     * The specified action keyword was invalid;
-     * force an abort through a DMH_FATAL notification...
+  private:
+    const char         *name;  // name of package tracked
+    pkgArchiveNameList *next;  // pointer to next stack entry
+
+  public:
+    inline bool NotRecorded( const char *candidate )
+    {
+      /* Walk the stack of tracked package names, to determine
+       * if an entry matching "candidate" is already present;
+       * returns false if such an entry exists, otherwise true
+       * to indicate that it may be added as a unique entry.
+       */
+      pkgArchiveNameList *check = this;
+      while( check != NULL )
+      {
+       /* We haven't walked off the bottom of the stack yet...
+        */
+       if( strcmp( check->name, candidate ) == 0 )
+       {
+         /* ...and the current entry matches the candidate;
+          * thus the candidate will not be stacked, so we
+          * may discard it from the heap.
+          */
+         free( (void *)(candidate) );
+
+         /* We've found a match, so there is no point in
+          * continuing the search; simply return false to
+          * signal rejection of the candidate.
+          */
+         return false;
+       }
+       /* No matching entry found yet; walk down to the next
+        * stack entry, if any, to continue the search.
+        */
+       check = check->next;
+      }
+      /* We walked off the bottom of the stack, without finding
+       * any match; return true to accept the candidate.
+       */
+      return true;
+    }
+    inline pkgArchiveNameList *Record( const char *candidate )
+    {
+      /* Add a new entry at the top of the stack, to record
+       * the processing of an archive named by "candidate";
+       * on entry "this" is the current stack pointer, and
+       * we return the new stack pointer, referring to the
+       * added entry which becomes the new top of stack.
+       */
+      pkgArchiveNameList *retptr = new pkgArchiveNameList();
+      retptr->name = candidate; retptr->next = this;
+      return retptr;
+    }
+    inline ~pkgArchiveNameList()
+    {
+      /* Completely clear the stack, releasing the heap memory
+       * allocated to record the stacked package names.
+       */
+      free( (void *)(name) );
+      delete next;
+    }
+};
+
+static pkgArchiveNameList *pkgProcessedArchives; // stack pointer
+
+EXTERN_C int climain( int argc, char **argv )
+{
+  try
+  { /* Set up the diagnostic message handler, using the console's
+     * `stderr' stream for notifications, and tagging messages with
+     * the program basename derived from argv[0]...
      */
-    dmh_notify( DMH_FATAL, "%s: unknown action keyword\n", *argv );
+    char *dmh_progname;
+    char progname[ 1 + strlen( dmh_progname = strdup( basename( *argv++ ))) ];
+    dmh_init( DMH_SUBSYSTEM_TTY, strcpy( progname, dmh_progname ) );
+    free( dmh_progname );
 
-  /* If we get to here, then the specified action identifies a
-   * valid operation; load the package database, according to the
-   * local `profile' configuration, and invoke the operation.
-   */
-  const char *dfile;
-  if( access( dfile = xmlfile( profile_key ), R_OK ) != 0 )
-  {
-    /* The user hasn't provided a custom configuration profile...
+    /* Interpret the `action keyword', specifying the action to be
+     * performed on this invocation...
      */
-    dmh_notify( DMH_WARNING, "%s: user configuration file missing\n", dfile );
+    int action = action_code( *argv );
+    if( action < 0 )
+    {
+      /* No valid action keyword was found; force an abort
+       * through an appropriate DMH_FATAL notification...
+       */
+      if( *argv == NULL )
+       /*
+        * ...viz. the user didn't specify any argument, which
+        * could have been interpreted as an action keyword.
+        */
+       dmh_notify( DMH_FATAL, "no action specified\n" );
 
-    /* ...release the memory allocated by xmlfile(), to store its path name,
-     * then try the mingw-get distribution default profile instead.
+      else
+       /* ...or, the specified action keyword was invalid.
+        */
+       dmh_notify( DMH_FATAL, "%s: unknown action keyword\n", *argv );
+    }
+
+    /* If we get to here, then the specified action identifies a
+     * valid operation; load the package database, according to the
+     * local `profile' configuration, and invoke the operation.
      */
-    free( (void *)(dfile) );
-    dmh_notify( DMH_INFO, "%s: trying system default configuration\n",
-       dfile = xmlfile( defaults_key ) );
+    const char *dfile;
+    if( access( dfile = xmlfile( profile_key ), R_OK ) != 0 )
+    {
+      /* The user hasn't provided a custom configuration profile...
+       */
+      dmh_notify( DMH_WARNING, "%s: user configuration file missing\n", dfile );
+
+      /* ...release the memory allocated by xmlfile(), to store its path name,
+       * then try the mingw-get distribution default profile instead.
+       */
+      free( (void *)(dfile) );
+      dmh_notify( DMH_INFO, "%s: trying system default configuration\n",
+         dfile = xmlfile( defaults_key ) );
+    }
+
+    pkgXmlDocument dbase( dfile );
+    if( dbase.IsOk() )
+    {
+      /* We successfully loaded the basic settings...
+       * The configuration file name was pushed on to the heap,
+       * by xmlfile(); we don't need that any more, (because it
+       * is reproduced within the database image itself), so
+       * free the heap copy, to avoid memory leaks.
+       */
+      free( (void *)(dfile) );
+
+      /* Merge all package lists, as specified in the "repository"
+       * section of the "profile", into the XML database tree...
+       */
+      if( dbase.BindRepositories( action == ACTION_UPDATE ) == NULL )
+       /*
+        * ...bailing out, on an invalid profile specification...
+        */
+       dmh_notify( DMH_FATAL, "%s: invalid application profile\n", dbase.Value() );
+
+      /* If the requested action was "update", then we've already done it,
+       * as a side effect of binding the cached repository catalogues...
+       */
+      if( action != ACTION_UPDATE )
+      {
+       /* ...otherwise, we need to load the system map...
+        */
+       dbase.LoadSystemMap();
+
+       /* ...initialise any preferences which the user may
+        * have specified within profile.xml...
+        */
+       dbase.EstablishPreferences( "cli" );
+
+       /* ...and invoke the appropriate action handler.
+        */
+       switch( action )
+       {
+         case ACTION_LIST:
+         case ACTION_SHOW:
+           /*
+            * "list" and "show" actions are synonymous;
+            * invoke the info-display handler.
+            */
+           dbase.DisplayPackageInfo( argc, argv );
+           break;
+
+         case ACTION_SOURCE:
+         case ACTION_LICENCE:
+           /*
+            * Process the "source" or "licence" request for one
+            * or more packages; begin with an empty stack of names,
+            * for tracking packages as processed.
+            */
+           pkgProcessedArchives = NULL;
+           if( pkgOptions()->Test( OPTION_ALL_RELATED ) )
+           {
+             /* The "--all-related" option is in effect; ensure
+              * that all dependencies will be evaluated, as if to
+              * perform a recursive reinstall...
+              */
+             pkgOptions()->SetFlags( OPTION_ALL_DEPS );
+             /*
+              * ...then, for each package which is identified on
+              * the command line...
+              */
+             while( --argc )
+               /*
+                * ...schedule a request to install the package,
+                * together with all of its runtime dependencies...
+                */
+               dbase.Schedule( ACTION_INSTALL, *++argv );
+
+             /* ...but DON'T proceed with installation; rather
+              * process the "source" or "licence" request for
+              * each scheduled package.
+              */
+             dbase.GetScheduledSourceArchives( (unsigned long)(action) );
+           }
+           else while( --argc )
+             /*
+              * The "--all-related" option is NOT in effect; simply
+              * process the "source" or "licence" request exclusively
+              * in respect of each package named on the command line.
+              */
+             dbase.GetSourceArchive( *++argv, (unsigned long)(action) );
+
+           /* Finally, clear the stack of processed package names.
+            */
+           delete pkgProcessedArchives;
+           break;
+
+         case ACTION_UPGRADE:
+           if( argc < 2 )
+             /*
+              * This is a special case of the upgrade request, for which
+              * no explicit package names have been specified; in this case
+              * we retrieve the list of all installed packages, scheduling
+              * each of them for upgrade...
+              */
+             dbase.RescheduleInstalledPackages( ACTION_UPGRADE );
+
+           /* ...subsequently falling through to complete the action,
+            * using the default processing mechanism; (note that in this
+            * case no further scheduling will be performed, because there
+            * are no additional package names specified in the argv list).
+            */
+         default:
+           /* ...schedule the specified action for each additional command line
+            * argument, (each of which is assumed to represent a package name)...
+            */
+           while( --argc )
+             /*
+              * (Skipped if argv < 2 on entry).
+              */
+             dbase.Schedule( (unsigned long)(action), *++argv );
+
+           /* ...finally, execute all scheduled actions, and update the
+            * system map accordingly.
+            */
+           dbase.ExecuteActions();
+           dbase.UpdateSystemMap();
+       }
+      }
+      /* If we get this far, then all actions completed successfully;
+       * we are done.
+       */
+      return EXIT_SUCCESS;
+    }
+
+    /* If we get to here, then the package database load failed;
+     * once more, we force an abort through a DMH_FATAL notification...
+     *
+     * Note: although dmh_notify does not return, in the DMH_FATAL case,
+     * GCC cannot know this, so we pretend that it gives us a return value,
+     * to avoid a possible warning about reaching the end of a non-void
+     * function without a return value assignment...
+     */
+    return dmh_notify( DMH_FATAL, "%s: cannot load configuration\n", dfile );
   }
 
-  pkgXmlDocument dbase( dfile );
-  if( dbase.IsOk() )
+  catch( dmh_exception &e )
   {
-    /* We successfully loaded the basic settings...
-     * The configuration file name was pushed on to the heap,
-     * by xmlfile(); we don't need that any more, (because it
-     * is reproduced within the database image itself), so
-     * free the heap copy, to avoid memory leaks.
+    /* An error occurred; it should already have been diagnosed,
+     * so simply bail out.
      */
-    free( (void *)(dfile) );
+    return EXIT_FAILURE;
+  }
+}
 
-    /* Merge all package lists, as specified in the "repository"
-     * section of the "profile", into the XML database tree...
-     */
-    if( dbase.BindRepositories( action == ACTION_UPDATE ) == NULL )
-      /*
-       * ...bailing out, on an invalid profile specification...
-       */
-      dmh_notify( DMH_FATAL, "%s: invalid application profile\n", dbase.Value() );
+#include "pkgproc.h"
 
-    /* If the requested action was "update", then we've already done it,
-     * as a side effect of binding the cached repository catalogues...
-     */
-    if( action != ACTION_UPDATE )
+void pkgActionItem::GetSourceArchive( pkgXmlNode *package, unsigned long category )
+{
+  /* Handle a 'mingw-get source ...' or a 'mingw-get licence ...' request
+   * in respect of the source code or licence archive for a single package.
+   */
+  const char *src = package->SourceArchiveName( category );
+  if( (src != NULL) && pkgProcessedArchives->NotRecorded( src ) )
+  {
+    if( pkgOptions()->Test( OPTION_PRINT_URIS ) == OPTION_PRINT_URIS )
     {
-      /* ...otherwise, we need to load the system map...
+      /* The --print-uris option is in effect; this is all
+       * that we are expected to do.
+       */
+      PrintURI( src );
+    }
+
+    else
+    { /* The --print-uris option is not in effect; we must at
+       * least check that the source package is available in the
+       * source archive cache, and if not, download it...
        */
-      dbase.LoadSystemMap();
+      const char *path_template; flags |= ACTION_DOWNLOAD;
+      DownloadSingleArchive( src, path_template = (category == ACTION_SOURCE)
+         ? pkgSourceArchivePath() : pkgArchivePath()
+       );
 
-      /* ...schedule the specified action for each additional command line
-       * argument, (each of which is assumed to represent a package name)...
+      /* ...then, unless the --download-only option is in effect...
        */
-      while( --argc )
-       dbase.Schedule( (unsigned long)(action), *++argv );
+      if( pkgOptions()->Test( OPTION_DOWNLOAD_ONLY ) != OPTION_DOWNLOAD_ONLY )
+      {
+       /* ...we establish the current working directory as the
+        * destination where it should be unpacked...
+        */
+       char source_archive[mkpath( NULL, path_template, src, NULL )];
+       mkpath( source_archive, path_template, src, NULL );
 
-      /* ...finally, execute all scheduled actions, and update the
-       * system map accordingly.
+       /* ...and extract the content from the source archive.
+        */
+       pkgTarArchiveExtractor unpack( source_archive, "." );
+      }
+      /* The path_template was allocated on the heap; we are
+       * done with it, so release the memory allocation...
        */
-      dbase.ExecuteActions();
-      dbase.UpdateSystemMap();
+      free( (void *)(path_template) );
     }
 
-    /* If we get this far, then all actions completed successfully;
-     * we are done.
+    /* Record the current archive name as "processed", so we may
+     * avoid any inadvertent duplicate processing.
      */
-    return EXIT_SUCCESS;
+    pkgProcessedArchives = pkgProcessedArchives->Record( src );
   }
+}
 
-  /* If we get to here, then the package database load failed;
-   * once more, we force an abort through a DMH_FATAL notification...
-   *
-   * Note: although dmh_notify does not return, in the DMH_FATAL case,
-   * GCC cannot know this, so we pretend that it gives us a return value,
-   * to avoid a possible warning about reaching the end of a non-void
-   * function without a return value assignment...
+void pkgActionItem::GetScheduledSourceArchives( unsigned long category )
+{
+  /* Process "source" or "licence" requests in respect of a list of
+   * packages, (scheduled as if for installation); this is the handler
+   * for the case when the "--all-related" option is in effect for a
+   * "source" or "licence" request.
    */
-  return dmh_notify( DMH_FATAL, "%s: cannot load configuration\n", dfile );
+  pkgActionItem *scheduled = this;
+  while( scheduled->prev != NULL ) scheduled = scheduled->prev;
 
+  /* For each scheduled list entry...
+   */
+  while( scheduled != NULL )
+  {
+    /* ...process the "source" or "licence" request, as appropriate,
+     * in respect of the associated package...
+     */
+    scheduled->GetSourceArchive( scheduled->Selection(), category );
+    /*
+     * ...then move on to the next entry, if any.
+     */
+    scheduled = scheduled->next;
   }
-  catch (dmh_exception &e)
+}
+
+void pkgXmlDocument::GetSourceArchive( const char *name, unsigned long category )
+{
+  /* Look up a named package reference in the XML catalogue,
+   * then forward it as a pkgActionItem, for processing of an
+   * associated "source" or "licence" request.
+   */ 
+  pkgXmlNode *pkg = FindPackageByName( name );
+  if( pkg->IsElementOfType( package_key ) )
   {
-    return EXIT_FAILURE;
+    /* We found a top-level specification for the required package...
+     */
+    pkgXmlNode *component = pkg->FindFirstAssociate( component_key );
+    if( component != NULL )
+      /*
+       * When this package is subdivided into components,
+       * then we derive the source reference from the first
+       * component defined.
+       */
+      pkg = component;
+  }
+
+  /* Now inspect the "release" specifications within the
+   * selected package/component definition...
+   */
+  if( (pkg = pkg->FindFirstAssociate( release_key )) != NULL )
+  {
+    /* ...creating a pkgActionItem...
+     */
+    pkgActionItem latest;
+    pkgXmlNode *selected = pkg;
+
+    /* ...and examining each release in turn...
+     */
+    while( pkg != NULL )
+    {
+      /* ...select the most recent release, and assign it
+       * to the pkgActionItem reference...
+       */
+      if( latest.SelectIfMostRecentFit( pkg ) == pkg )
+       latest.SelectPackage( selected = pkg );
+
+      /* ...continuing until we have examined all available
+       * release specifications.
+       */
+      pkg = pkg->FindNextAssociate( release_key );
+    }
+
+    /* Finally, hand off the "source" or "licence" processing
+     * request, based on the most recent release selection, to
+     * the pkgActionItem we've just instantiated.
+     */
+    latest.GetSourceArchive( selected, category );
   }
 }