diagnostics.zsh 11.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# diagnostics.zsh
#
# Diagnostic and debugging support for oh-my-zsh

# omz_diagnostic_dump()
#
# Author: Andrew Janke <andrew@apjanke.net>
#
# Usage:
#
# omz_diagnostic_dump [-v] [-V] [file]
#
# NOTE: This is a work in progress. Its interface and behavior are going to change,
# and probably in non-back-compatible ways.
#
# Outputs a bunch of information about the state and configuration of
# oh-my-zsh, zsh, and the user's system. This is intended to provide a
# bunch of context for diagnosing your own or a third party's problems, and to
# be suitable for posting to public bug reports.
#
# The output is human-readable and its format may change over time. It is not
# suitable for parsing. All the output is in one single file so it can be posted
# as a gist or bug comment on GitHub. GitHub doesn't support attaching tarballs
# or other files to bugs; otherwise, this would probably have an option to produce
# tarballs that contain copies of the config and customization files instead of
# catting them all in to one file.
#
# This is intended to be widely portable, and run anywhere that oh-my-zsh does.
# Feel free to report any portability issues as bugs.
#
# This is written in a defensive style so it still works (and can detect) cases when
# basic functionality like echo and which have been redefined. In particular, almost
# everything is invoked with "builtin" or "command", to work in the face of user 
# redefinitions.
#
# OPTIONS
#
# [file]   Specifies the output file. If not given, a file in the current directory
#        is selected automatically.
#
# -v    Increase the verbosity of the dump output. May be specified multiple times.
#       Verbosity levels:
#        0 - Basic info, shell state, omz configuration, git state
#        1 - (default) Adds key binding info and configuration file contents
#        2 - Adds zcompdump file contents
#
# -V    Reduce the verbosity of the dump output. May be specified multiple times.
#
# TODO:
# * Multi-file capture
# * Add automatic gist uploading
# * Consider whether to move default output file location to TMPDIR. More robust
#     but less user friendly.
#
55
56
57

autoload -Uz is-at-least

58
59
60
function omz_diagnostic_dump() {
  emulate -L zsh

61
62
  builtin echo "Generating diagnostic dump; please be patient..."
  
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
  local thisfcn=omz_diagnostic_dump
  local -A opts
  local opt_verbose opt_noverbose opt_outfile
  local timestamp=$(date +%Y%m%d-%H%M%S)
  local outfile=omz_diagdump_$timestamp.txt
  builtin zparseopts -A opts -D -- "v+=opt_verbose" "V+=opt_noverbose"
  local verbose n_verbose=${#opt_verbose} n_noverbose=${#opt_noverbose}
  (( verbose = 1 + n_verbose - n_noverbose ))

  if [[ ${#*} > 0 ]]; then
    opt_outfile=$1
  fi
  if [[ ${#*} > 1 ]]; then
    builtin echo "$thisfcn: error: too many arguments" >&2
    return 1
  fi
  if [[ -n "$opt_outfile" ]]; then
    outfile="$opt_outfile"
  fi

  # Always write directly to a file so terminal escape sequences are
  # captured cleanly
  _omz_diag_dump_one_big_text &> "$outfile"
  if [[ $? != 0 ]]; then
    builtin echo "$thisfcn: error while creating diagnostic dump; see $outfile for details"
  fi

  builtin echo
  builtin echo Diagnostic dump file created at: "$outfile"
  builtin echo
  builtin echo To share this with OMZ developers, post it as a gist on GitHub 
  builtin echo at "https://gist.github.com" and share the link to the gist.
  builtin echo
  builtin echo "WARNING: This dump file contains all your zsh and omz configuration files,"
  builtin echo "so don't share it publicly if there's sensitive information in them."
  builtin echo

}

function _omz_diag_dump_one_big_text() {
  local program programs progfile md5

  builtin echo oh-my-zsh diagnostic dump
  builtin echo
107
108
109
  builtin echo $outfile
  builtin echo 
  
110
111
112
113
  # Basic system and zsh information
  command date
  command uname -a
  builtin echo OSTYPE=$OSTYPE
114
  builtin echo ZSH_VERSION=$ZSH_VERSION
115
  builtin echo User: $USER
116
  builtin echo umask: $(umask)
117
  builtin echo
118
119
  _omz_diag_dump_os_specific_version
  builtin echo
120
121

  # Installed programs
122
  programs=(sh zsh ksh bash sed cat grep ls find git posh)
123
  local progfile="" extra_str="" sha_str=""
124
  for program in $programs; do
125
    extra_str="" sha_str=""
126
127
128
    progfile=$(builtin which $program)
    if [[ $? == 0 ]]; then
      if [[ -e $progfile ]]; then
129
130
131
132
        if builtin whence shasum &>/dev/null; then
          sha_str=($(command shasum $progfile))
          sha_str=$sha_str[1]
          extra_str+=" SHA $sha_str"
133
134
        fi
        if [[ -h "$progfile" ]]; then
135
          extra_str+=" ( -> ${progfile:A} )"
136
137
138
139
140
141
142
143
        fi
      fi
      builtin printf '%-9s %-20s %s\n' "$program is" "$progfile" "$extra_str"
    else
      builtin echo "$program: not found"
    fi
  done
  builtin echo
144
  builtin echo Command Versions:
145
  builtin echo "zsh: $(zsh --version)"
146
  builtin echo "this zsh session: $ZSH_VERSION"
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
  builtin echo "bash: $(bash --version | command grep bash)"
  builtin echo "git: $(git --version)"
  builtin echo "grep: $(grep --version)"
  builtin echo

  # Core command definitions
  _omz_diag_dump_check_core_commands || return 1
  builtin echo  

  # ZSH Process state
  builtin echo Process state:
  builtin echo pwd: $PWD
  if builtin whence pstree &>/dev/null; then
    builtin echo Process tree for this shell:
    pstree -p $$
  else
    ps -fT
  fi
  builtin set | command grep -a '^\(ZSH\|plugins\|TERM\|LC_\|LANG\|precmd\|chpwd\|preexec\|FPATH\|TTY\|DISPLAY\|PATH\)\|OMZ'
  builtin echo
  #TODO: Should this include `env` instead of or in addition to `export`?
  builtin echo Exported:
  builtin echo $(builtin export | command sed 's/=.*//')
  builtin echo 
  builtin echo Locale:
  command locale
  builtin echo

175
  # Zsh installation and configuration
176
177
178
  builtin echo Zsh configuration:
  builtin echo setopt: $(builtin setopt)
  builtin echo
179
180
181
  builtin echo zstyle:
  builtin zstyle
  builtin echo
182
183
184
185
186
187
  builtin echo 'compaudit output:'
  compaudit
  builtin echo 
  builtin echo '$fpath directories:'
  command ls -lad $fpath
  builtin echo
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216

  # Oh-my-zsh installation
  builtin echo oh-my-zsh installation:
  command ls -ld ~/.z*
  command ls -ld ~/.oh*
  builtin echo
  builtin echo oh-my-zsh git state:
  (cd $ZSH && builtin echo "HEAD: $(git rev-parse HEAD)" && git remote -v && git status | command grep "[^[:space:]]")
  if [[ $verbose -ge 1 ]]; then
    (cd $ZSH && git reflog --date=default | command grep pull)
  fi
  builtin echo
  if [[ -e $ZSH_CUSTOM ]]; then
    local custom_dir=$ZSH_CUSTOM
    if [[ -h $custom_dir ]]; then
      custom_dir=$(cd $custom_dir && pwd -P)
    fi
    builtin echo "oh-my-zsh custom dir:"
    builtin echo "   $ZSH_CUSTOM ($custom_dir)"
    (cd ${custom_dir:h} && command find ${custom_dir:t} -name .git -prune -o -print)
    builtin echo
  fi

  # Key binding and terminal info
  if [[ $verbose -ge 1 ]]; then
    builtin echo "bindkey:"
    builtin bindkey
    builtin echo
    builtin echo "infocmp:"
217
    command infocmp -L
218
219
220
221
222
223
224
    builtin echo
  fi

  # Configuration file info
  local zdotdir=${ZDOTDIR:-$HOME}
  builtin echo "Zsh configuration files:"
  local cfgfile cfgfiles
225
226
  # Some files for bash that zsh does not use are intentionally included
  # to help with diagnosing behavior differences between bash and zsh
227
  cfgfiles=( /etc/zshenv /etc/zprofile /etc/zshrc /etc/zlogin /etc/zlogout 
228
    $zdotdir/.zshenv $zdotdir/.zprofile $zdotdir/.zshrc $zdotdir/.zlogin $zdotdir/.zlogout
Andrew Janke's avatar
Andrew Janke committed
229
    ~/.zsh.pre-oh-my-zsh
230
    /etc/bashrc /etc/profile ~/.bashrc ~/.profile ~/.bash_profile ~/.bash_logout )
231
232
233
234
235
236
237
  command ls -lad $cfgfiles 2>&1
  builtin echo
  if [[ $verbose -ge 1 ]]; then
    for cfgfile in $cfgfiles; do
      _omz_diag_dump_echo_file_w_header $cfgfile
    done
  fi
238
  builtin echo
239
240
241
242
243
244
245
246
247
248
249
250
251
252
  builtin echo "Zsh compdump files:"
  local dumpfile dumpfiles
  command ls -lad $zdotdir/.zcompdump*
  dumpfiles=( $zdotdir/.zcompdump*(N) )
  if [[ $verbose -ge 2 ]]; then
    for dumpfile in $dumpfiles; do
      _omz_diag_dump_echo_file_w_header $dumpfile
    done
  fi

}

function _omz_diag_dump_check_core_commands() {
  builtin echo "Core command check:"
253
  local redefined name builtins externals reserved_words
254
255
256
257
258
259
260
  redefined=()
  # All the zsh non-module builtin commands
  # These are taken from the zsh reference manual for 5.0.2
  # Commands from modules should not be included.
  # (For back-compatibility, if any of these are newish, they should be removed,
  # or at least made conditional on the version of the current running zsh.)
  # "history" is also excluded because OMZ is known to redefine that
261
262
263
  reserved_words=( do done esac then elif else fi for case if while function 
    repeat time until select coproc nocorrect foreach end '!' '[[' '{' '}' 
    )
264
265
  builtins=( alias autoload bg bindkey break builtin bye cd chdir command
    comparguments compcall compctl compdescribe compfiles compgroups compquote comptags
266
267
268
269
270
    comptry compvalues continue dirs disable disown echo echotc echoti emulate
    enable eval exec exit false fc fg functions getln getopts hash
    jobs kill let limit log logout noglob popd print printf
    pushd pushln pwd r read rehash return sched set setopt shift
    source suspend test times trap true ttyctl type ulimit umask unalias
271
272
    unfunction unhash unlimit unset unsetopt vared wait whence where which zcompile
    zle zmodload zparseopts zregexparse zstyle )
273
274
275
276
277
  if is-at-least 5.1; then
    reserved_word+=( declare export integer float local readonly typeset )
  else
    builtins+=( declare export integer float local readonly typeset )
  fi
278
279
  builtins_fatal=( builtin command local )
  externals=( zsh )
280
281
282
283
284
285
286
  for name in $reserved_words; do
    if [[ $(builtin whence -w $name) != "$name: reserved" ]]; then
      builtin echo "reserved word '$name' has been redefined"
      builtin which $name
      redefined+=$name
    fi
  done
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
  for name in $builtins; do
    if [[ $(builtin whence -w $name) != "$name: builtin" ]]; then
      builtin echo "builtin '$name' has been redefined"
      builtin which $name
      redefined+=$name
    fi
  done
  for name in $externals; do
    if [[ $(builtin whence -w $name) != "$name: command" ]]; then
      builtin echo "command '$name' has been redefined"
      builtin which $name
      redefined+=$name
    fi
  done

  if [[ -n "$redefined" ]]; then
    builtin echo "SOME CORE COMMANDS HAVE BEEN REDEFINED: $redefined"
  else
    builtin echo "All core commands are defined normally"
  fi

}

function _omz_diag_dump_echo_file_w_header() {
  local file=$1
  if [[ ( -f $file || -h $file ) ]]; then
    builtin echo "========== $file =========="
    if [[ -h $file ]]; then
      builtin echo "==========    ( => ${file:A} )   =========="
    fi
    command cat $file
    builtin echo "========== end $file =========="
    builtin echo
320
321
322
323
324
325
  elif [[ -d $file ]]; then
    builtin echo "File '$file' is a directory"
  elif [[ ! -e $file ]]; then
    builtin echo "File '$file' does not exist"
  else
    command ls -lad "$file"
326
327
328
  fi
}

329
330
331
332
333
334
335
336
337
function _omz_diag_dump_os_specific_version() {
  local osname osver version_file version_files
  case "$OSTYPE" in
    darwin*)
      osname=$(command sw_vers -productName)
      osver=$(command sw_vers -productVersion)      
      builtin echo "OS Version: $osname $osver build $(sw_vers -buildVersion)"
      ;;
    cygwin)
338
      command systeminfo | command head -4 | command tail -2
339
340
341
342
343
344
345
346
347
348
349
350
351
352
      ;;
  esac

  if builtin which lsb_release >/dev/null; then
    builtin echo "OS Release: $(command lsb_release -s -d)"
  fi

  version_files=( /etc/*-release(N) /etc/*-version(N) /etc/*_version(N) )
  for version_file in $version_files; do
    builtin echo "$version_file:"
    command cat "$version_file"
    builtin echo
  done
}
353