OSDN Git Service

Add scripting hooks to support creation of MS-Windows shortcuts.
[mingw/mingw-get.git] / src / clistub.c
index dbea42f..b0e593e 100644 (file)
@@ -4,7 +4,7 @@
  * $Id$
  *
  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
- * Copyright (C) 2009, 2010, MinGW Project
+ * Copyright (C) 2009, 2010, 2011, 2012, MinGW Project
  *
  *
  * Initiation stub for command line invocation of mingw-get
@@ -27,6 +27,8 @@
 #define _WIN32_WINNT 0x500
 #define WIN32_LEAN_AND_MEAN
 
+#include "debug.h"
+
 #include <windows.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <process.h>
 #include <getopt.h>
 
+#include "pkgopts.h"
+
 #define EXIT_FATAL  EXIT_FAILURE + 1
 
+static const char *progname;
+
 wchar_t *AppPathNameW( const wchar_t *relpath )
 {
   /* UTF-16LE implementation; NOT thread safe...
@@ -154,15 +160,313 @@ wchar_t *AppPathNameW( const wchar_t *relpath )
 
 extern const char *version_identification;
 
-#define  IMPLEMENT_INITIATION_RITES
+static const char *help_text =
+"Manage MinGW and MSYS installations (command line user interface).\n\n"
+
+"Usage:\n"
+"  mingw-get [OPTIONS] ACTION [package-spec ...]\n\n"
+
+"  mingw-get update\n"
+"  mingw-get [OPTIONS] {install | upgrade | remove} package-spec ...\n"
+"  mingw-get [OPTIONS] {show | list} [package-spec ...]\n\n"
+
+"Options:\n"
+"  --help, -h        Show this help text\n"
+"\n"
+"  --version, -V     Show version and licence information\n"
+"\n"
+"  --verbose, -v     Increase verbosity of diagnostic or\n"
+"                    progress reporting output; repeat up\n"
+"                    to three times for maximum verbosity\n"
+"  --verbose=N       Set verbosity level to N; (0 <= N <= 3)\n"
+"\n"
+
+/* The "--trace" option is available only when dynamic tracing
+ * debugging support is compiled in; don't advertise it otherwise.
+ */
+#if DEBUG_ENABLED( DEBUG_TRACE_DYNAMIC )
+"  --trace=N         Enable tracing feature N; (debugging aid)\n"
+"\n"
+#endif
+
+/* The following are always available...
+ */
+"  --reinstall       When performing an install or upgrade\n"
+"                    operation, reinstall any named package\n"
+"                    for which the most recent release is\n"
+"                    already installed\n"
+"\n"
+"  --recursive       Extend the scope of \"install --reinstall\"\n"
+"                    or of \"upgrade\", such that the operation\n"
+"                    is applied recursively to all prerequisites\n"
+"                    of all packages named on the command line\n"
+"\n"
+"  --download-only   Download the package archive files which\n"
+"                    would be required to complete the specified\n"
+"                    install, upgrade, or source operation, but\n"
+"                    do not unpack them, or otherwise proceed\n"
+"                    to complete the operation\n"
+"\n"
+"  --print-uris      Display the repository URIs from which\n"
+"                    package archive files would be retrieved\n"
+"                    prior to performing the specified install,\n"
+"                    upgrade, or source operation, but do not\n"
+"                    download any package file, or otherwise\n"
+"                    proceed with the operation\n"
+"\n"
+"  --all-related     When performing source or licence operations,\n"
+"                    causes mingw-get to retrieve, and optionally to\n"
+"                    unpack the source or licence archives for all\n"
+"                    runtime prerequisites of, and in addition to,\n"
+"                    the nominated package\n"
+"\n"
+"  --desktop[=all-users]\n"
+"                    Enable the creation of desktop shortcuts, for\n"
+"                    packages which provide the capability via pre-\n"
+"                    or post-install scripts; the optional 'all-users'\n"
+"                    qualifier requests that all such shortcuts are\n"
+"                    to be made available to all users; without it\n"
+"                    shortcuts will be created for current user only\n"
+"\n"
+"                    Note that specification of this option does not\n"
+"                    guarantee that shortcuts will be created; the\n"
+"                    onus lies with individual package maintainers\n"
+"                    to provide scripting to support this capability\n"
+"\n"
+"  --start-menu[=all-users]\n"
+"                    Enable the creation of start menu shortcuts, for\n"
+"                    packages which provide the capability via pre-\n"
+"                    or post-install scripts; the optional 'all-users'\n"
+"                    qualifier requests that all such shortcuts are\n"
+"                    to be made available to all users; without it\n"
+"                    shortcuts will be created for current user only\n"
+"\n"
+"                    Note that specification of this option does not\n"
+"                    guarantee that shortcuts will be created; the\n"
+"                    onus lies with individual package maintainers\n"
+"                    to provide scripting to support this capability\n"
+"\n"
+"Actions:\n"
+"  update            Update local copy of repository catalogues\n"
+"  list, show        List and show details of available packages\n"
+"  source            Download and optionally unpack package sources\n"
+"  licence           Download and optionally unpack licence packages,\n"
+"                    handling them as if they are source packages\n"
+"  install           Install new packages\n"
+"  upgrade           Upgrade previously installed packages\n"
+"  remove            Remove previously installed packages\n\n"
+
+"Package Specifications:\n"
+"  [subsystem-]name[-component]:\n"
+"  msys-bash-doc     The 'doc' component of the bash package for MSYS\n"
+"  mingw32-gdb       All components of the gdb package for MinGW\n\n"
+
+"Use 'mingw-get list' to identify possible package names\n"
+"and the components associated with each.\n\n";
+
+#define  IMPLEMENT_INITIATION_RITES    PHASE_ONE_RITES
 #include "rites.c"
 
+static __inline__ __attribute__((__always_inline__))
+char **cli_setargv( HMODULE my_dll, struct pkgopts *opts, char **argv )
+{
+  /* A local wrapper function to facilitate passing pre-parsed
+   * command line options while performing the "climain()" call
+   * into mingw-get-0.dll
+   *
+   * Note that this requires a version of mingw-get-0.dll which
+   * provides the "cli_setopts()" hook...
+   */
+  typedef void (*dll_hook)(struct pkgopts *);
+  dll_hook cli_setopts = (dll_hook)(GetProcAddress( my_dll, "cli_setopts" ));
+  if( cli_setopts != NULL )
+    /*
+     * ...which allows us to pass the pre-parsed options.
+     */
+    cli_setopts( opts );
+
+  /* In any case, we always return the argument vector which is
+   * to be passed in the "climain()" call itself.
+   */
+  return argv;
+}
+
+/* FIXME: We may ultimately choose to factor the following atoi() replacement
+ * into a separate, free-standing module.  For now we keep it here, as a static
+ * function, but we keep the required #include adjacent.
+ */
+#include <ctype.h>
+
+static int xatoi( const char *input )
+{
+  /* A replacement for the standard atoi() function; this implementation
+   * supports conversion of octal or hexadecimal representations, in addition
+   * to the decimal representation required by standard atoi().
+   *
+   * We begin by initialising the result accumulator to zero...
+   */
+  int result = 0;
+
+  /* ...then, provided we have an input string to interpret...
+   */
+  if( input != NULL )
+  {
+    /* ...we proceed with interpretation and accumulation of the result,
+     * noting that we may have to handle an initial minus sign, (but we
+     * don't know yet, so assume that not for now).
+     */
+    int negate = 0;
+    while( isspace( *input ) )
+      /*
+       * Ignore leading white space.
+       */
+      ++input;
+
+    if( *input == '-' )
+      /*
+       * An initial minus sign requires negation
+       * of the accumulated result...
+       */
+      negate = *input++;
+
+    else if( *input == '+' )
+      /*
+       * ...while an initial plus sign is redundant,
+       * and may simply be ignored.
+       */
+      ++input;
+
+    if( *input == '0' )
+    {
+      /* An initial zero signifies either hexadecimal
+       * or octal representation...
+       */
+      if( (*++input | '\x20') == 'x' )
+       /*
+        * ...with following 'x' or 'X' indicating
+        * the hexadecimal case.
+        */
+       while( isxdigit( *++input ) )
+       {
+         /* Interpret sequence of hexadecimal digits...
+          */
+         result = (result << 4) + *input;
+         if( *input > 'F' )
+           /*
+            * ...with ASCII to binary conversion for
+            * lower case digits 'a'..'f',...
+            */
+           result -= 'a' - 10;
+
+         else if( *input > '9' )
+           /*
+            * ...ASCII to binary conversion for
+            * upper case digits 'A'..'F',...
+            */
+           result -= 'A' - 10;
+
+         else
+           /* ...or ASCII to decimal conversion,
+            * as appropriate.
+            */
+           result -= '0';
+       }
+      else while( (*input >= '0') && (*input < '8') )
+       /*
+        * Interpret sequence of octal digits...
+        */
+       result = (result << 3) + *input++ - '0';
+    }
+
+    else while( isdigit( *input ) )
+      /*
+       * Interpret sequence of decimal digits...
+       */
+      result = (result << 1) + (result << 3) + *input++ - '0';
+
+    if( negate )
+      /*
+       * We had an initial minus sign, so we must
+       * return the negated result...
+       */
+      return (0 - result);
+  }
+  /* ...otherwise, we simply return the accumulated result
+   */
+  return result;
+}
+
+static void set_script_hook( const char *hook, const char *optarg )
+{
+  /* Helper function to initialise the environment variables which
+   * are associated with Lua scripting hooks, when the user specifies
+   * the appropriate activation options on the command line.
+   */
+  if( optarg != NULL )
+  {
+    /* An optional argument was assigned for the hook...
+     */
+    int arglen = strlen( optarg );
+    const char *all_users = "all-users";
+    const char *value_none = "none";
+    if( strncmp( optarg, all_users, arglen ) == 0 )
+    {
+      /* When this is the "all-users" qualifier, we append it to
+       * the value to be assigned to the environment variable.
+       */
+      const char *fmt = "%s --%s";
+      char tmp[1 + snprintf( NULL, 0, fmt, hook, all_users )];
+      snprintf( tmp, sizeof( tmp ), fmt, hook, all_users );
+      putenv( tmp );
+    }
+    else if( strncmp( optarg, value_none, arglen ) == 0 )
+    {
+      /* When it is the "none" qualifier, we remove any prior
+       * assignment to the respective environment variable.
+       *
+       * FIXME: to support assignment from within profile.xml,
+       * we will eventually need additional coding here, to
+       * override any profile.xml assignment.
+       */
+      char tmp[strlen( hook )];
+      char *p = tmp;
+      do { *p++ = *hook;
+        } while( *hook++ != '=' );
+      *p = '\0';
+      putenv( tmp );
+    }
+    else
+    { /* No other qualifier is supported; diagnose and ignore.
+       */
+      while( *hook++ != '=' ) /* advance pointer; no other action */;
+      fprintf( stderr,
+         "%s: *** WARNING *** invalid argument '%s' to option %s ignored\n",
+         progname, optarg, hook
+       );
+    }
+  }
+  else
+    /* No qualifying option argument specified; simply assign the
+     * hook variable value, as passed from the getopts() handler.
+     */
+    putenv( hook );
+}
+
+#define atmost( lim, val )             ((lim) < (val)) ? (lim) : (val)
+
 int main( int argc, char **argv )
 {
-  /* Make a note of...
+  /* Provide storage for interpretation of any parsed command line options.
+   * Note that we could also initialise them here, but then we would need to
+   * give attention to the number of initialisers required; to save us that
+   * concern we will defer to an initialisation loop, below.
    */
-  const char *progname = basename( *argv );    /* ...this program's name    */
-  wchar_t *approot;                            /* and where it is installed */
+  struct pkgopts parsed_options;
+
+  /* Make a note of this program's name, and where it's installed.
+   */
+  wchar_t *approot;
+  progname = basename( *argv );
 
   if( argc > 1 )
   {
@@ -170,12 +474,32 @@ int main( int argc, char **argv )
      * Interpret any which specify processing options for this application,
      * (these are all specified in GNU `long only' style).
      */
+    int optref;
     struct option options[] =
     {
-      /* Option Name             Argument Category    Store To   Return Value
-       * ----------------------   ------------------   --------   ------------
+      /* Option Name      Argument Category    Store To   Return Value
+       * --------------   ------------------   --------   ------------------
        */
-      { "version",               no_argument,         NULL,      'V'          },
+      { "version",        no_argument,         NULL,      'V'                },
+      { "help",           no_argument,         NULL,      'h'                },
+      { "verbose",        optional_argument,   NULL,      OPTION_VERBOSE     },
+
+      { "recursive",      no_argument,         &optref,   OPTION_RECURSIVE   },
+      { "reinstall",      no_argument,         &optref,   OPTION_REINSTALL   },
+      { "download-only",  no_argument,         &optref,   OPTION_DNLOAD_ONLY },
+      { "print-uris",     no_argument,         &optref,   OPTION_PRINT_URIS  },
+
+      { "all-related",    no_argument,         &optref,   OPTION_ALL_RELATED },
+
+      { "desktop",        optional_argument,   NULL,      'D'                },
+      { "start-menu",     optional_argument,   NULL,      'M'                },
+
+#     if DEBUG_ENABLED( DEBUG_TRACE_DYNAMIC )
+       /* The "--trace" option is supported only when dynamic tracing
+        * debugging support has been compiled in.
+        */
+      { "trace",          required_argument,   &optref,   OPTION_TRACE       },
+#     endif
 
       /* This list must be terminated by a null definition...
        */
@@ -183,7 +507,17 @@ int main( int argc, char **argv )
     };
 
     int opt, offset;
-    while( (opt = getopt_long_only( argc, argv, "V", options, &offset )) != -1 )
+    for( opt = OPTION_FLAGS; opt < OPTION_TABLE_SIZE; ++opt )
+      /*
+       * Ensure that all entries within the options table are initialised
+       * to zero, (equivalent to NULL for pointer entries)...
+       */
+      parsed_options.flags[opt].numeric = 0;
+
+    while( (opt = getopt_long_only( argc, argv, "vVh", options, &offset )) != -1 )
+      /*
+       * Parse any user specified options from the command line...
+       */
       switch( opt )
       {
        case 'V':
@@ -193,6 +527,98 @@ int main( int argc, char **argv )
          printf( version_identification );
          return EXIT_SUCCESS;
 
+       case 'h':
+         /* This is a request to display help text and quit.
+          */
+         printf( help_text );
+         return EXIT_SUCCESS;
+
+       case OPTION_VERBOSE:
+         /* This is a request to set an explicit verbosity level,
+          * (minimum zero, maximum three), or if no explicit argument
+          * is specified, to increment verbosity as does "-v".
+          */
+         if( optarg != NULL )
+         {
+           /* This is the case where an explicit level was specified...
+            */
+           parsed_options.flags[OPTION_FLAGS].numeric =
+             (parsed_options.flags[OPTION_FLAGS].numeric & ~OPTION_VERBOSE)
+             | atmost( OPTION_VERBOSE_MAX, xatoi( optarg ));
+           break;
+         }
+
+       case 'v':
+         /* This is a request to increment the verbosity level
+          * from its initial zero setting, to a maximum of three.
+          */
+         if( (parsed_options.flags[OPTION_FLAGS].numeric & OPTION_VERBOSE) < 3 )
+           ++parsed_options.flags[OPTION_FLAGS].numeric;
+         break;
+
+       case 'D':
+         /* This is a request to enable, or disable, the Lua scripting
+          * hook for installation of desktop shortcuts.
+          */
+         set_script_hook( "MINGW_GET_DESKTOP_HOOK=--desktop", optarg );
+         break;
+
+       case 'M':
+         /* This is a request to enable, or disable, the Lua scripting
+          * hook for installation of start menu shortcuts.
+          */
+         set_script_hook( "MINGW_GET_START_MENU_HOOK=--start-menu", optarg );
+         break;
+
+       case OPTION_GENERIC:
+         switch( optref & OPTION_STORAGE_CLASS )
+         {
+           /* This represents a generic option specification,
+            * allowing for storage of a option argument of the
+            * specified class into a specified option slot...
+            */
+           unsigned shift;
+
+           case OPTION_STORE_STRING:
+             /* This is a simple store of a option argument
+              * which represents a character string.
+              */
+             parsed_options.flags[optref & 0xfff].string = optarg;
+             break;
+
+           case OPTION_STORE_NUMBER:
+             /* This is also a simple store of the argument value,
+              * in this case interpreted as a number.
+              */
+             parsed_options.flags[optref & 0xfff].numeric = xatoi( optarg );
+             break;
+
+           case OPTION_MERGE_NUMBER:
+             /* In this case, we combine the value of the argument,
+              * again interpreted as a number, with the original value
+              * stored in the option slot, forming the bitwise logical
+              * .OR. of the pair of values.
+              */
+             parsed_options.flags[optref & 0xfff].numeric |= xatoi( optarg );
+             break;
+
+           default:
+             /* This is a mask and store operation for a specified
+              * bit-field within the first pair of flags slots; in
+              * this case, the optref value itself specifies a 12-bit
+              * value, a 12-bit combining mask, and an alignment shift
+              * count between 0 and 52, in 4-bit increments.
+              */
+             if( (shift = (optref & OPTION_SHIFT_MASK) >> 22) < 53 )
+             {
+               uint64_t value = optref & 0xfff;
+               uint64_t mask = (optref & 0xfff000) >> 12;
+               *(uint64_t *)(parsed_options.flags) &= ~(mask << shift);
+               *(uint64_t *)(parsed_options.flags) |= value << shift;
+             }
+         }
+         break;
+
        default:
          /* User specified an invalid or unsupported option...
           */
@@ -216,7 +642,7 @@ int main( int argc, char **argv )
     putenv( approot_setup );
   }
 
-  if( --argc )
+  if( argc > 1 )
   {
     /* The user specified arguments on the command line...
      * we load the supporting DLL into the current process context,
@@ -224,6 +650,7 @@ int main( int argc, char **argv )
      * command line processing routine...
      */
     int lock;
+    char *argv_base = *argv;
     typedef int (*dll_entry)( int, char ** );
     HMODULE my_dll = LoadLibraryW( AppPathNameW( MINGW_GET_DLL ) );
     dll_entry climain = (dll_entry)(GetProcAddress( my_dll, "climain" ));
@@ -237,6 +664,16 @@ int main( int argc, char **argv )
       return EXIT_FATAL;
     }
 
+    /* Adjust argc and argv to discount parsed options...
+     */
+    argc -= optind;
+    argv += optind - 1;
+    /*
+     * ...while preserving the original argv[0] reference within
+     * the first remaining argument to be passed to climain().
+     */
+    *argv = argv_base;
+
     /* We want only one mingw-get process accessing the XML database
      * at any time; attempt to acquire an exclusive access lock...
      */
@@ -245,7 +682,7 @@ int main( int argc, char **argv )
       /* ...and proceed, only if successful.
        *  A non-zero return value indicates that a fatal error occurred.
        */
-      int rc = climain( argc, argv );
+      int rc = climain( argc, cli_setargv( my_dll, &parsed_options, argv ) );
 
       /* We must release the mingw-get DLL code, BEFORE we invoke
        * last rites processing, (otherwise the last rites clean-up
@@ -278,8 +715,9 @@ int main( int argc, char **argv )
     /* If we get to here, then the GUI could not be started...
      * Issue a diagnostic message, before abnormal termination.
      */
-    fprintf( stderr, "%s: %S: unable to start application; status = %d\n",
-       progname, MINGW_GET_GUI, status
+    fprintf( stderr,
+       "%s: %S: unable to start GUI; helper program not installed\n",
+       progname, MINGW_GET_GUI
       );
     return EXIT_FATAL;
   }