Merge pull request #201 from gregory-nisbet/feature-env-shells

--env: support more shells, allow explicit shell selection
This commit is contained in:
Mike McQuaid 2016-09-04 21:23:33 +01:00 committed by GitHub
commit dfcbefff73
10 changed files with 207 additions and 24 deletions

View File

@ -3,6 +3,7 @@
require "extend/ENV" require "extend/ENV"
require "build_environment" require "build_environment"
require "utils/shell"
module Homebrew module Homebrew
def __env def __env
@ -11,12 +12,24 @@ module Homebrew
ENV.setup_build_environment ENV.setup_build_environment
ENV.universal_binary if ARGV.build_universal? ENV.universal_binary if ARGV.build_universal?
if $stdout.tty? shell_value = ARGV.value("shell")
if ARGV.include?("--plain")
shell = nil
elsif shell_value.nil?
# legacy behavior
shell = :bash unless $stdout.tty?
elsif shell_value == "auto"
shell = Utils::Shell.parent_shell || Utils::Shell.preferred_shell
elsif shell_value
shell = Utils::Shell.path_to_shell(shell_value)
end
env_keys = build_env_keys(ENV)
if shell.nil?
dump_build_env ENV dump_build_env ENV
else else
build_env_keys(ENV).each do |key| env_keys.each { |key| puts Utils::Shell.export_value(shell, key, ENV[key]) }
puts "export #{key}=\"#{ENV[key]}\""
end
end end
end end
end end

View File

@ -0,0 +1,10 @@
# return the shell profile file based on users' preference shell
def shell_profile
opoo "shell_profile has been deprecated in favor of Utils::Shell.profile"
case ENV["SHELL"]
when %r{/(ba)?sh} then "~/.bash_profile"
when %r{/zsh} then "~/.zshrc"
when %r{/ksh} then "~/.kshrc"
else "~/.bash_profile"
end
end

View File

@ -3,6 +3,7 @@ require "language/python"
require "formula" require "formula"
require "version" require "version"
require "development_tools" require "development_tools"
require "utils/shell"
module Homebrew module Homebrew
module Diagnostic module Diagnostic
@ -476,7 +477,7 @@ module Homebrew
Consider setting your PATH so that #{HOMEBREW_PREFIX}/bin Consider setting your PATH so that #{HOMEBREW_PREFIX}/bin
occurs before /usr/bin. Here is a one-liner: occurs before /usr/bin. Here is a one-liner:
echo 'export PATH="#{HOMEBREW_PREFIX}/bin:$PATH"' >> #{shell_profile} #{Utils::Shell.prepend_path_in_shell_profile("#{HOMEBREW_PREFIX}/bin")}
EOS EOS
end end
end end
@ -496,7 +497,7 @@ module Homebrew
<<-EOS.undent <<-EOS.undent
Homebrew's bin was not found in your PATH. Homebrew's bin was not found in your PATH.
Consider setting the PATH for example like so Consider setting the PATH for example like so
echo 'export PATH="#{HOMEBREW_PREFIX}/bin:$PATH"' >> #{shell_profile} #{Utils::Shell.prepend_path_in_shell_profile("#{HOMEBREW_PREFIX}/bin")}
EOS EOS
end end
@ -511,7 +512,7 @@ module Homebrew
Homebrew's sbin was not found in your PATH but you have installed Homebrew's sbin was not found in your PATH but you have installed
formulae that put executables in #{HOMEBREW_PREFIX}/sbin. formulae that put executables in #{HOMEBREW_PREFIX}/sbin.
Consider setting the PATH for example like so Consider setting the PATH for example like so
echo 'export PATH="#{HOMEBREW_PREFIX}/sbin:$PATH"' >> #{shell_profile} #{Utils::Shell.prepend_path_in_shell_profile("#{HOMEBREW_PREFIX}/sbin")}
EOS EOS
end end

View File

@ -202,7 +202,7 @@ module Homebrew
SSL_CERT_DIR support was removed from Apple's curl. SSL_CERT_DIR support was removed from Apple's curl.
If fetching formulae fails you should: If fetching formulae fails you should:
unset SSL_CERT_DIR unset SSL_CERT_DIR
and remove it from #{shell_profile} if present. and remove it from #{Utils::Shell.shell_profile} if present.
EOS EOS
end end

View File

@ -1,3 +1,5 @@
require "utils/shell"
module FormulaCellarChecks module FormulaCellarChecks
def check_PATH(bin) def check_PATH(bin)
# warn the user if stuff was installed outside of their PATH # warn the user if stuff was installed outside of their PATH
@ -12,7 +14,7 @@ module FormulaCellarChecks
<<-EOS.undent <<-EOS.undent
#{prefix_bin} is not in your PATH #{prefix_bin} is not in your PATH
You can amend this by altering your #{shell_profile} file You can amend this by altering your #{Utils::Shell.shell_profile} file
EOS EOS
end end

View File

@ -227,6 +227,26 @@ class IntegrationCommandTests < Homebrew::TestCase
cmd("--env")) cmd("--env"))
end end
def test_env_bash
assert_match %r{export CMAKE_PREFIX_PATH="#{Regexp.quote(HOMEBREW_PREFIX.to_s)}"},
cmd("--env", "--shell=bash")
end
def test_env_fish
assert_match %r{set [-]gx CMAKE_PREFIX_PATH "#{Regexp.quote(HOMEBREW_PREFIX.to_s)}"},
cmd("--env", "--shell=fish")
end
def test_env_csh
assert_match %r{setenv CMAKE_PREFIX_PATH #{Regexp.quote(HOMEBREW_PREFIX.to_s)};},
cmd("--env", "--shell=tcsh")
end
def test_env_plain
assert_match %r{CMAKE_PREFIX_PATH: #{Regexp.quote(HOMEBREW_PREFIX)}},
cmd("--env", "--plain")
end
def test_prefix_formula def test_prefix_formula
assert_match "#{HOMEBREW_CELLAR}/testball", assert_match "#{HOMEBREW_CELLAR}/testball",
cmd("--prefix", testball) cmd("--prefix", testball)

View File

@ -0,0 +1,59 @@
require "testing_env"
require "utils/shell"
class ShellSmokeTest < Homebrew::TestCase
def test_path_to_shell()
# raw command name
assert_equal :bash, Utils::Shell.path_to_shell("bash")
# full path
assert_equal :bash, Utils::Shell.path_to_shell("/bin/bash")
# versions
assert_equal :zsh, Utils::Shell.path_to_shell("zsh-5.2")
# strip newline too
assert_equal :zsh, Utils::Shell.path_to_shell("zsh-5.2\n")
end
def test_path_to_shell_failure()
assert_equal nil, Utils::Shell.path_to_shell("")
assert_equal nil, Utils::Shell.path_to_shell("@@@@@@")
assert_equal nil, Utils::Shell.path_to_shell("invalid_shell-4.2")
end
def test_sh_quote()
assert_equal "''", Utils::Shell.sh_quote("")
assert_equal "\\\\", Utils::Shell.sh_quote("\\")
assert_equal "'\n'", Utils::Shell.sh_quote("\n")
assert_equal "\\$", Utils::Shell.sh_quote("$")
assert_equal "word", Utils::Shell.sh_quote("word")
end
def test_csh_quote()
assert_equal "''", Utils::Shell.csh_quote("")
assert_equal "\\\\", Utils::Shell.csh_quote("\\")
# note this test is different than for sh
assert_equal "'\\\n'", Utils::Shell.csh_quote("\n")
assert_equal "\\$", Utils::Shell.csh_quote("$")
assert_equal "word", Utils::Shell.csh_quote("word")
end
def prepend_path_shell(shell, path, fragment)
original_shell = ENV["SHELL"]
ENV["SHELL"] = shell
prepend_message = Utils::Shell.prepend_path_in_shell_profile(path)
assert(
prepend_message.start_with?(fragment),
"#{shell}: expected #{prepend_message} to match #{fragment}"
)
ENV["SHELL"] = original_shell
end
def test_prepend_path_in_shell_profile()
prepend_path_shell "/bin/tcsh", "/path", "echo 'setenv PATH /path"
prepend_path_shell "/bin/bash", "/path", "echo 'export PATH=\"/path"
prepend_path_shell "/usr/local/bin/fish", "/path", "echo 'set -g fish_user_paths \"/path\" $fish_user_paths' >>"
end
end

View File

@ -1,6 +1,7 @@
require "testing_env" require "testing_env"
require "utils" require "utils"
require "tempfile" require "tempfile"
require "utils/shell"
class TtyTests < Homebrew::TestCase class TtyTests < Homebrew::TestCase
def test_strip_ansi def test_strip_ansi
@ -157,15 +158,15 @@ class UtilTests < Homebrew::TestCase
def test_shell_profile def test_shell_profile
ENV["SHELL"] = "/bin/sh" ENV["SHELL"] = "/bin/sh"
assert_equal "~/.bash_profile", shell_profile assert_equal "~/.bash_profile", Utils::Shell.shell_profile
ENV["SHELL"] = "/bin/bash" ENV["SHELL"] = "/bin/bash"
assert_equal "~/.bash_profile", shell_profile assert_equal "~/.bash_profile", Utils::Shell.shell_profile
ENV["SHELL"] = "/bin/another_shell" ENV["SHELL"] = "/bin/another_shell"
assert_equal "~/.bash_profile", shell_profile assert_equal "~/.bash_profile", Utils::Shell.shell_profile
ENV["SHELL"] = "/bin/zsh" ENV["SHELL"] = "/bin/zsh"
assert_equal "~/.zshrc", shell_profile assert_equal "~/.zshrc", Utils::Shell.shell_profile
ENV["SHELL"] = "/bin/ksh" ENV["SHELL"] = "/bin/ksh"
assert_equal "~/.kshrc", shell_profile assert_equal "~/.kshrc", Utils::Shell.shell_profile
end end
def test_popen_read def test_popen_read

View File

@ -526,16 +526,6 @@ def paths
end.uniq.compact end.uniq.compact
end end
# return the shell profile file based on users' preference shell
def shell_profile
case ENV["SHELL"]
when %r{/(ba)?sh} then "~/.bash_profile"
when %r{/zsh} then "~/.zshrc"
when %r{/ksh} then "~/.kshrc"
else "~/.bash_profile"
end
end
def disk_usage_readable(size_in_bytes) def disk_usage_readable(size_in_bytes)
if size_in_bytes >= 1_073_741_824 if size_in_bytes >= 1_073_741_824
size = size_in_bytes.to_f / 1_073_741_824 size = size_in_bytes.to_f / 1_073_741_824

View File

@ -0,0 +1,87 @@
module Utils
SHELL_PROFILE_MAP = {
:bash => "~/.bash_profile",
:csh => "~/.cshrc",
:fish => "~/.config/fish/config.fish",
:ksh => "~/.kshrc",
:sh => "~/.bash_profile",
:tcsh => "~/.tcshrc",
:zsh => "~/.zshrc",
}.freeze
module Shell
UNSAFE_SHELL_CHAR = /([^A-Za-z0-9_\-.,:\/@\n])/
# take a path and heuristically convert it
# to a shell name, return nil if there's no match
def self.path_to_shell(path)
# we only care about the basename
shell_name = File.basename(path)
# handle possible version suffix like `zsh-5.2`
shell_name.sub!(/-.*\z/m, "")
shell_name.to_sym if %w[bash csh fish ksh sh tcsh zsh].include?(shell_name)
end
def self.preferred_shell
path_to_shell(ENV.fetch("SHELL", ""))
end
def self.parent_shell
path_to_shell(`ps -p #{Process.ppid} -o ucomm=`.strip)
end
def self.csh_quote(str)
# ruby's implementation of shell_escape
str = str.to_s
return "''" if str.empty?
str = str.dup
# anything that isn't a known safe character is padded
str.gsub!(UNSAFE_SHELL_CHAR, "\\\\" + "\\1")
# newlines have to be specially quoted in csh
str.gsub!(/\n/, "'\\\n'")
str
end
def self.sh_quote(str)
# ruby's implementation of shell_escape
str = str.to_s
return "''" if str.empty?
str = str.dup
# anything that isn't a known safe character is padded
str.gsub!(UNSAFE_SHELL_CHAR, "\\\\" + "\\1")
str.gsub!(/\n/, "'\n'")
str
end
# quote values. quoting keys is overkill
def self.export_value(shell, key, value)
case shell
when :bash, :ksh, :sh, :zsh
"export #{key}=\"#{sh_quote(value)}\""
when :fish
# fish quoting is mostly Bourne compatible except that
# a single quote can be included in a single-quoted string via \'
# and a literal \ can be included via \\
"set -gx #{key} \"#{sh_quote(value)}\""
when :csh, :tcsh
"setenv #{key} #{csh_quote(value)};"
end
end
# return the shell profile file based on users' preferred shell
def self.shell_profile
SHELL_PROFILE_MAP.fetch(preferred_shell, "~/.bash_profile")
end
def self.prepend_path_in_shell_profile(path)
case preferred_shell
when :bash, :ksh, :sh, :zsh, nil
"echo 'export PATH=\"#{sh_quote(path)}:$PATH\"' >> #{shell_profile}"
when :csh, :tcsh
"echo 'setenv PATH #{csh_quote(path)}:$PATH' >> #{shell_profile}"
when :fish
"echo 'set -g fish_user_paths \"#{sh_quote(path)}\" $fish_user_paths' >> #{shell_profile}"
end
end
end
end