help-octave
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

implementation of 'shell' in octave-3.6.1/share/octave/3.6.1/m/pkg/pkg.m


From: Sergei Steshenko
Subject: implementation of 'shell' in octave-3.6.1/share/octave/3.6.1/m/pkg/pkg.m makes package installation problems undebuggable
Date: Wed, 16 May 2012 05:18:17 -0700 (PDT)

Hello,

we have already discussed the issue, but, alas, it needs attention yet again.

There are a lot of complaints in this list about problems installing packages 
and, of course, people installing packages and having problems with that 
publish screen output.

Alas, the screen output often makes no sense.

It makes no sense for a very simple and good reason:

1) 'configure' and 'make' are run through a function called 'shell';
2) the 'shell' function implementation is broken by design.

In order to understand the "broken by design" statement one has to remember 
that modern OSes have _both_ stderr and stdout streams. Programs like 
'configure' and 'make' call other programs (e.g. compilers, linkers, etc) and 
this whole set of processes outputs to _both_ stderr and stdout - not 
simultaneously to both of them but some output goes to stdout (e.g. compiler 
invocation  string by 'make') and some output goes to stderr (e.g. compiler 
warning and error messages).

For a human to understand what is happening it's of utmost importance to see 
those lines in the order they are produced.

Alas, 'shell' is implemented this way:


   2270 function [status, output] = shell (cmd)
   2271   persistent have_sh;
   2272
   2273   cmd = strrep (cmd, "\\", "/");
   2274   if (ispc () && ! isunix ())
   2275     if (isempty(have_sh))
   2276       if (system("sh.exe -c \"exit\""))
   2277         have_sh = false;
   2278       else
   2279         have_sh = true;
   2280       endif
   2281     endif
   2282     if (have_sh)
   2283       [status, output] = system (cstrcat ("sh.exe -c \"", cmd, "\""));
   2284     else
   2285       error ("Can not find the command shell");
   2286     endif
   2287   else
   2288     [status, output] = system (cmd);
   2289   endif
   2290 endfunction


let's for simplicity consider just line #2288 - this is the line does the job 
on UNIXish systems.

If one checks what 'system' does, he/she can see that _only_ stdout is 
captured. So, when, say, 'make' is called in 'configure_make' function:


   1380     ## Make.
   1381     if (exist (fullfile (src, "Makefile"), "file"))
   1382       [status, output] = shell (cstrcat (scenv, "make -C '", src, "'"));
   1383       if (status != 0)
   1384         rm_rf (desc.dir);
   1385         error ("'make' returned the following error: %s", output);
   1386       elseif (verbose)
   1387         printf("%s", output);
   1388       endif
   1389     endif


, the following happens:

1) 'make' runs, and its stderr is _not_ being captured into 'output', so stderr 
output appears on the screen _immediately_;
2) when 'make' completes, its stdout is output to screen either by line #1385 
or #1387 (if at all).


So, error messages appear _before_ the commands producing them, and this makes 
debugging virtually impossible. Exactly because stdout is captured and stderr 
is not, the 'shell' function implementation is broken by design. And AFAIK this 
broken implementation has been in Octave for years.


Below one can find a set of functions I wrote. The set resolves the problem of 
stderr not being captured. The '__shell_with_grabbed_stdout_stderr__' function 
from the set should be used instead of 'shell'.

Regards,
  Sergei.


The functions:


##################################################################
#
# The following '__shell_with_grabbed_stdout_stderr__' is added by
# Sergei Steshenko - the purpose is to have orderly screen output
# produced by 'cmd'. The built-in 'system' grabs only stdout.
# In this file thi causes out of order output during 'configure'
# and 'make'.
#
# According to
# 
http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/redirection.mspx?mfr=true
# some_cmd >stdout.log 2>&1
# - I don't have Windows Octave to check that the whole thing
# works.
#
# The function is based on 'shell' function originally present in
# this file.
#
##################################################################

function [tmp_dir_for_stdout_stderr, shell_stdout_stderr_file] = 
__create_tmp_dir_for_stdout_stderr___()
  TMP = getenv("TMP");
  if(numel(TMP) ~= 0)
    if(exist(TMP, "dir") ~= 7)
      error("TMP=%s directory does not exist - either create it or unset TMP 
environment variable", TMP);
    endif

    tmp_dir_for_stdout_stderr = tmpnam(TMP, "oct-");
    last_dir_path_element = 
tmp_dir_for_stdout_stderr(rindex(tmp_dir_for_stdout_stderr, filesep) + 1:end); 
#fprintf(stderr, "__shell_with_grabbed_stdout_stderr__: 
last_dir_path_element=%s\n", last_dir_path_element);
  else
    tmp_dir_for_stdout_stderr = tmpnam;
  endif

  shell_stdout_stderr_file = cstrcat(tmp_dir_for_stdout_stderr, 
"/shell_stdout_stderr.log"); # fprintf(stderr, 
"__shell_with_grabbed_stdout_stderr__: shell_stdout_stderr_file=%s\n", 
shell_stdout_stderr_file);

  if(numel(TMP) == 0)
    [st, msg, msgid] = mkdir(tmp_dir_for_stdout_stderr);

    if(st == 0)
      error("could not create '%s' directory, system message: %s, msgid=%d", 
tmp_dir_for_stdout_stderr, msg, msgid);
    endif
  else
    [st, msg, msgid] = mkdir(TMP, last_dir_path_element);

    if(st == 0)
      error("could not create '%s' directory, system message: %s, msgid=%d", 
tmp_dir_for_stdout_stderr, msg, msgid);
    endif
  endif

  #fprintf(stderr, "__shell_with_grabbed_stdout_stderr__: 
shell_stdout_stderr_file=%s\n", shell_stdout_stderr_file);
endfunction

function [status, output] = __execute_command_in_shell__(shell_and_command)
  [tmp_dir_for_stdout_stderr, shell_stdout_stderr_file] = 
__create_tmp_dir_for_stdout_stderr___();
  [status, output] = system(cstrcat(shell_and_command, " >", 
shell_stdout_stderr_file, " 2>&1"));
  [fid, msg] = fopen(shell_stdout_stderr_file, "r");
  if(fid == -1)
    error("cannot open '$s' file for reading, system message: %s", 
shell_stdout_stderr_file, msg);
  endif

  output = char(fread(fid));

  fclose(fid);
  [err, msg] = unlink(shell_stdout_stderr_file)
  if(err)
    warning("__shell_with_grabbed_stdout_stderr__: could not remove '%s' 
file\n", shell_stdout_stderr_file);
  endif

  [st, msg, msgid] = rmdir(tmp_dir_for_stdout_stderr);
  if(st == 0)
    warning("__shell_with_grabbed_stdout_stderr__: could not remove '%' 
directory\n", tmp_dir_for_stdout_stderr);
  endif
endfunction

function [status, output] = __shell_with_grabbed_stdout_stderr__(cmd)
  persistent have_sh;

  cmd = strrep (cmd, "\\", "/");
  # Sergei - the above is essentially wrong. On a UNIXish system '\'
  # can appear as escape character, and in such a case it shouldn't
  # be replaced with '/'. But I just copy-pasted the line from
  # 'shell' function originally present in this file.

  if(ispc() && !isunix())
    if(isempty(have_sh))
      if(system("sh.exe -c \"exit\""))
        have_sh = false;
      else
        have_sh = true;
      endif
    endif

    if(have_sh)
      [status, output] = __execute_command_in_shell__(cstrcat("sh.exe -c \"", 
cmd, "\""));
    else
      error ("Can not find the command shell");
    endif
  else
    [status, output] = __execute_command_in_shell__(cmd);
  endif
endfunction


reply via email to

[Prev in Thread] Current Thread [Next in Thread]