Skip to content

Commit

Permalink
Add Platform#has_command? and Platform#run_command
Browse files Browse the repository at this point in the history
For has_command?, we can cache output & use Process.find_executable instead of calling a bunch of shell subprocesses for a bit of speed up
  • Loading branch information
maxfierke committed Apr 28, 2024
1 parent f20098e commit 42fc1be
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 80 deletions.
85 changes: 13 additions & 72 deletions src/mstrap/dsl/system.cr
Original file line number Diff line number Diff line change
@@ -1,83 +1,24 @@
module MStrap
module DSL
module System
# Executes a given command and waits for it to complete, returning whether
# the exit status indicated success.
#
# By default the process is configured with input, output, and error of
# the `mstrap` process.
#
# * _env_: optionally specifies the environment for the command
# * _command_: specifies the command to run. Arguments are allowed here, if
# _args_ are omitted and will be evaluated by the system shell.
# * _args_: optionally specifies arguments for the command. These will not
# be processed by the shell.
# * _shell_: specifies whether to run the command through the system shell
# * _input_: specifies where to direct STDIN on the spawned process
# * _output_: specifies where to direct STDOUT on the spawned process
# * _error_: specifies where to direct STDERR on the spawned process
# * _quiet_: If passed as `true`, it does no logging. If `mstrap` is
# running in debug mode, process output is always logged.
# * _sudo_: specifies whether to run the command with superuser privileges
# See `MStrap::Platform#has_command?`
def has_command?(command_name : String, **kwargs) : Bool
MStrap::Platform.has_command?(command_name, **kwargs)
end

# See `MStrap::Platform#run_command`
def cmd(
env : Hash?,
command : String,
args : Array(String)?,
shell = true,
input = Process::Redirect::Inherit,
output = Process::Redirect::Inherit,
error = Process::Redirect::Inherit,
quiet = false,
sudo = false
**kwargs
)
if sudo
if args
args.unshift(command)
command = "sudo"
else
command = "sudo #{command}"
end
end

logd "+ #{env ? env : ""} #{command} #{args ? args.join(" ") : ""}"

named = {
shell: shell,
env: env,
input: input,
output: output,
error: error,
}

if MStrap.debug?
named = named.merge({
input: Process::Redirect::Inherit,
output: Process::Redirect::Inherit,
error: Process::Redirect::Inherit,
})
elsif quiet
named = named.merge({
input: Process::Redirect::Close,
output: Process::Redirect::Close,
error: Process::Redirect::Close,
})
end

child = Process.new(command, args, **named)

at_exit {
# Cleanup this process when we exit, if it's still running. (e.g. receiving SIGINT)
unless child.terminated?
# Reap the whole process group, otherwise nested processes may live
# to print output another day
pgid = Process.pgid(child.pid)
Process.signal(Signal::TERM, -pgid)
child.wait
end
}

status = child.wait
status.success?
MStrap::Platform.run_command(
env,
command,
args,
**kwargs
)
end

# :nodoc:
Expand Down
102 changes: 98 additions & 4 deletions src/mstrap/platform.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,108 @@ module MStrap
{{ raise "Unsupported platform" }}
{% end %}

@@found_commands = Hash(String, String).new

# Indicates whether the host platform has Git installed
def self.has_git?
ENV["MSTRAP_IGNORE_GIT"]? != "true" && (`command -v git` && $?.success?)
ENV["MSTRAP_IGNORE_GIT"]? != "true" && has_command?("git")
end

# Indicates whether the host platform has a given command available
#
# Lookups are cached by default, but cached info can be skipped by passing
# `skip_cache: true`
def self.has_command?(command_name : String, skip_cache : Bool = false) : Bool
if (cmd_path = @@found_commands[command_name]?) && !skip_cache
true
elsif cmd_path = Process.find_executable(command_name)
@@found_commands[command_name] = cmd_path
true
else
@@found_commands.delete(command_name)
false
end
end

# Indicates whether the host platform has GPG installed
def self.has_gpg?
!!(`command -v gpg` && $?.success?)
# Executes a given command and waits for it to complete, returning whether
# the exit status indicated success.
#
# By default the process is configured with input, output, and error of
# the `mstrap` process.
#
# * _env_: optionally specifies the environment for the command
# * _command_: specifies the command to run. Arguments are allowed here, if
# _args_ are omitted and will be evaluated by the system shell.
# * _args_: optionally specifies arguments for the command. These will not
# be processed by the shell.
# * _shell_: specifies whether to run the command through the system shell
# * _input_: specifies where to direct STDIN on the spawned process
# * _output_: specifies where to direct STDOUT on the spawned process
# * _error_: specifies where to direct STDERR on the spawned process
# * _quiet_: If passed as `true`, it does no logging. If `mstrap` is
# running in debug mode, process output is always logged.
# * _sudo_: specifies whether to run the command with superuser privileges
def self.run_command(
env : Hash?,
command : String,
args : Array(String)?,
shell = true,
input = Process::Redirect::Inherit,
output = Process::Redirect::Inherit,
error = Process::Redirect::Inherit,
quiet = false,
sudo = false
)
if sudo
if args
args.unshift(command)
command = "sudo"
else
command = "sudo #{command}"
end
end

Log.debug { "+ #{env ? env : ""} #{command} #{args ? args.join(" ") : ""}" }

named = {
shell: shell,
env: env,
input: input,
output: output,
error: error,
}

if MStrap.debug?
named = named.merge({
input: Process::Redirect::Inherit,
output: Process::Redirect::Inherit,
error: Process::Redirect::Inherit,
})
elsif quiet
named = named.merge({
input: Process::Redirect::Close,
output: Process::Redirect::Close,
error: Process::Redirect::Close,
})
end

child = Process.new(command, args, **named)

# TODO: Refactor this into something less hacky
# (e.g. push to a Deque used by a Process.on_terminate handler or something)
at_exit {
# Cleanup this process when we exit, if it's still running. (e.g. receiving SIGINT)
unless child.terminated?
# Reap the whole process group, otherwise nested processes may live
# to print output another day
pgid = Process.pgid(child.pid)
Process.signal(Signal::TERM, -pgid)
child.wait
end
}

status = child.wait
status.success?
end

# Installs a list of packages using the platform's package manager
Expand Down
4 changes: 2 additions & 2 deletions src/mstrap/supports/mise_installer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ module MStrap
end

def installed?
File.exists?(MISE_INSTALL_PATH) && (`command -v mise` && $?.success?)
File.exists?(MISE_INSTALL_PATH) && has_command?("mise")
end

private def fetch_installer!
Expand Down Expand Up @@ -82,7 +82,7 @@ module MStrap
end

private def verify_installer?
@verify_installer ||= MStrap::Platform.has_gpg?
has_command?("gpg")
end
end
end
2 changes: 1 addition & 1 deletion src/mstrap/supports/mkcert.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module MStrap

# Returns whether mkcert is installed
def installed?
cmd("command -v mkcert", quiet: true)
has_command?("mkcert")
end

# Runs mkcert install process to add CARoot, etc.
Expand Down
2 changes: 1 addition & 1 deletion src/mstrap/supports/rustup_installer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ module MStrap
end

def installed?
`command -v rustup` && $?.success?
has_command?("rustup")
end

private def fetch_installer!
Expand Down

0 comments on commit 42fc1be

Please sign in to comment.