module Fastlane
class EnvironmentPrinter
def self.output
env_info = get
# Remove sensitive option values
FastlaneCore::Configuration.sensitive_strings.compact.each do |sensitive_element|
env_info.gsub!(sensitive_element, "#########")
end
puts env_info
if FastlaneCore::Helper.mac? && UI.interactive? && UI.confirm("🙄 Wow, that's a lot of markdown text... should fastlane put it into your clipboard, so you can easily paste it on GitHub?")
copy_to_clipboard(env_info)
UI.success("Successfully copied markdown into your clipboard 🎨")
end
UI.success("Open https://github.com/fastlane/fastlane/issues/new to submit a new issue ✅")
end
def self.get
UI.important("Generating fastlane environment output, this might take a few seconds...")
require "fastlane/markdown_table_formatter"
env_output = ""
env_output << print_system_environment
env_output << print_system_locale
env_output << print_fastlane_files
env_output << print_loaded_fastlane_gems
env_output << print_loaded_plugins
env_output << print_loaded_gems
env_output << print_date
# Adding title
status = (env_output.include?("🚫") ? "🚫" : "✅")
env_header = "#{status} fastlane environment #{status}
\n\n"
env_tail = " "
final_output = ""
if FastlaneCore::Globals.captured_output?
final_output << "### Captured Output\n\n"
final_output << "Command Used: `#{ARGV.join(' ')}`\n"
final_output << "Output/Log
\n\n```\n\n#{FastlaneCore::Globals.captured_output}\n\n```\n\n \n\n"
end
final_output << env_header + env_output + env_tail
end
def self.print_date
date = Time.now.strftime("%Y-%m-%d")
"\n*generated on:* **#{date}**\n"
end
def self.print_loaded_plugins
ENV["FASTLANE_ENV_PRINTER"] = "enabled"
env_output = "### Loaded fastlane plugins:\n"
env_output << "\n"
plugin_manager = Fastlane::PluginManager.new
plugin_manager.load_plugins
if plugin_manager.available_plugins.length <= 0
env_output << "**No plugins Loaded**\n"
else
table = ""
table << "| Plugin | Version | Update-Status |\n"
table << "|--------|---------|\n"
plugin_manager.available_plugins.each do |plugin|
begin
installed_version = Fastlane::ActionCollector.determine_version(plugin)
latest_version = FastlaneCore::UpdateChecker.fetch_latest(plugin)
if Gem::Version.new(installed_version) == Gem::Version.new(latest_version)
update_status = "✅ Up-To-Date"
else
update_status = "🚫 Update available"
end
rescue
update_status = "💥 Check failed"
end
table << "| #{plugin} | #{installed_version} | #{update_status} |\n"
end
rendered_table = MarkdownTableFormatter.new table
env_output << rendered_table.to_md
end
env_output << "\n\n"
env_output
end
# We have this as a separate method, as this has to be handled
# slightly differently, depending on how fastlane is being called
def self.gems_to_check
if Helper.contained_fastlane?
Gem::Specification
else
Gem.loaded_specs.values
end
end
def self.print_loaded_fastlane_gems
# fastlanes internal gems
env_output = "### fastlane gems\n\n"
table = ""
table << "| Gem | Version | Update-Status |\n"
table << "|-----|---------|------------|\n"
fastlane_tools = Fastlane::TOOLS + [:fastlane_core, :credentials_manager]
gems_to_check.each do |current_gem|
update_status = "N/A"
next unless fastlane_tools.include?(current_gem.name.to_sym)
begin
latest_version = FastlaneCore::UpdateChecker.fetch_latest(current_gem.name)
if Gem::Version.new(current_gem.version) >= Gem::Version.new(latest_version)
update_status = "✅ Up-To-Date"
else
update_status = "🚫 Update available"
end
rescue
update_status = "💥 Check failed"
end
table << "| #{current_gem.name} | #{current_gem.version} | #{update_status} |\n"
end
rendered_table = MarkdownTableFormatter.new table
env_output << rendered_table.to_md
env_output << "\n\n"
return env_output
end
def self.print_loaded_gems
env_output = ""
env_output << "Loaded gems
\n\n"
table = "| Gem | Version |\n"
table << "|-----|---------|\n"
gems_to_check.each do |current_gem|
unless Fastlane::TOOLS.include?(current_gem.name.to_sym)
table << "| #{current_gem.name} | #{current_gem.version} |\n"
end
end
rendered_table = MarkdownTableFormatter.new table
env_output << rendered_table.to_md
env_output << " \n\n"
return env_output
end
def self.print_system_locale
env_output = "### System Locale\n\n"
found_one = false
env_table = ""
["LANG", "LC_ALL", "LANGUAGE"].each do |e|
env_icon = "🚫"
if ENV[e] && ENV[e].end_with?("UTF-8")
env_icon = "✅"
found_one = true
end
if ENV[e].nil?
env_icon = ""
end
env_table << "| #{e} | #{ENV[e]} | #{env_icon} |\n"
end
if !found_one
table = "| Error |\n"
table << "|-----|\n"
table << "| No Locale with UTF8 found 🚫|\n"
else
table = "| Variable | Value | |\n"
table << "|-----|---------|----|\n"
table << env_table
end
rendered_table = MarkdownTableFormatter.new table
env_output << rendered_table.to_md
env_output << "\n\n"
end
def self.print_system_environment
require "openssl"
env_output = "### Stack\n\n"
product, version, build = "Unknown"
os_version = "UNKNOWN"
if Helper.mac?
product, version, build = `sw_vers`.strip.split("\n").map { |line| line.split(':').last.strip }
os_version = version
end
if Helper.linux?
# this should work on pretty much all linux distros
os_version = `uname -a`.strip
version = ""
build = `uname -r`.strip
product = `cat /etc/issue.net`.strip
distro_guesser = {
fedora: "/etc/fedora-release",
debian_based: "/etc/debian_version",
suse: "/etc/SUSE-release",
mandrake: "/etc/mandrake-release"
}
distro_guesser.each do |dist, vers|
os_version = "#{dist} " + File.read(vers).strip if File.exist?(vers)
version = os_version
end
end
table_content = {
"fastlane" => Fastlane::VERSION,
"OS" => os_version,
"Ruby" => RUBY_VERSION,
"Bundler?" => Helper.bundler?,
"Git" => `git --version`.strip.split("\n").first,
"Installation Source" => anonymized_path($PROGRAM_NAME),
"Host" => "#{product} #{version} (#{build})",
"Ruby Lib Dir" => anonymized_path(RbConfig::CONFIG['libdir']),
"OpenSSL Version" => OpenSSL::OPENSSL_VERSION,
"Is contained" => Helper.contained_fastlane?.to_s,
"Is homebrew" => Helper.homebrew?.to_s,
"Is installed via Fabric.app" => Helper.mac_app?.to_s
}
if Helper.mac?
table_content["Xcode Path"] = anonymized_path(Helper.xcode_path)
begin
table_content["Xcode Version"] = Helper.xcode_version
rescue => ex
UI.error(ex)
UI.error("Could not get Xcode Version")
end
end
table = ["| Key | Value |"]
table += table_content.collect { |k, v| "| #{k} | #{v} |" }
begin
rendered_table = MarkdownTableFormatter.new(table.join("\n"))
env_output << rendered_table.to_md
rescue => ex
UI.error(ex)
UI.error("Error rendering markdown table using the following text:")
UI.message(table.join("\n"))
env_output << table.join("\n")
end
env_output << "\n\n"
env_output
end
def self.print_fastlane_files
env_output = "### fastlane files:\n\n"
fastlane_path = FastlaneCore::FastlaneFolder.fastfile_path
if fastlane_path && File.exist?(fastlane_path)
env_output << ""
env_output << "`#{fastlane_path}`
\n"
env_output << "\n"
env_output << "```ruby\n"
env_output << File.read(fastlane_path)
env_output << "```\n"
env_output << " "
else
env_output << "**No Fastfile found**\n"
end
env_output << "\n\n"
appfile_path = CredentialsManager::AppfileConfig.default_path
if appfile_path && File.exist?(appfile_path)
env_output << ""
env_output << "`#{appfile_path}`
\n"
env_output << "\n"
env_output << "```ruby\n"
env_output << File.read(appfile_path)
env_output << "```\n"
env_output << " "
else
env_output << "**No Appfile found**\n"
end
env_output << "\n\n"
env_output
end
def self.anonymized_path(path, home = ENV['HOME'])
return home ? path.gsub(%r{^#{home}(?=/(.*)|$)}, '~\2') : path
end
# Copy a given string into the clipboard
# Make sure to ask the user first, as some people don't
# use a clipboard manager, so they might lose something important
def self.copy_to_clipboard(string)
require 'open3'
Open3.popen3('pbcopy') { |input, _, _| input << string }
end
end
end