Skip to content

Commit

Permalink
Change how the git CLI subprocess is executed
Browse files Browse the repository at this point in the history
Signed-off-by: James Couball <jcouball@yahoo.com>
  • Loading branch information
jcouball committed Jan 14, 2024
1 parent f93e042 commit 17f49cc
Show file tree
Hide file tree
Showing 22 changed files with 1,191 additions and 550 deletions.
126 changes: 126 additions & 0 deletions .github/actions/show-context/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# https://help.github.com/en/articles/metadata-syntax-for-github-actions
name: 'Dump context'
description: 'GitHub Action composite to dump context'
author: 'crazy-max'
branding:
color: 'yellow'
icon: 'activity'

runs:
using: "composite"
steps:
-
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
await core.group(`Env vars`, async () => {
const envs = Object.keys(process.env).sort().reduce(
(obj, key) => {
obj[key] = process.env[key];
return obj;
}, {}
);
core.info(JSON.stringify(Object.fromEntries(Object.entries(envs).filter(([key]) => !key.startsWith('GHACTION_DCTX_') && !key.startsWith('INPUT_'))), null, 2));
});
await core.group(`GitHub context`, async () => {
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_GITHUB_CONTEXT}`), null, 2));
});
await core.group(`Job context`, async () => {
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_JOB_CONTEXT}`), null, 2));
});
await core.group(`Steps context`, async () => {
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_STEPS_CONTEXT}`), null, 2));
});
await core.group(`Runner context`, async () => {
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_RUNNER_CONTEXT}`), null, 2));
});
await core.group(`Strategy context`, async () => {
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_STRATEGY_CONTEXT}`), null, 2));
});
await core.group(`Matrix context`, async () => {
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_MATRIX_CONTEXT}`), null, 2));
});
await core.group(`Docker info`, async () => {
await exec.exec('docker', ['info'], {ignoreReturnCode: true}).catch(error => {
core.info(error);
});
});
await core.group(`Docker version`, async () => {
await exec.exec('docker', ['version'], {ignoreReturnCode: true}).catch(error => {
core.info(error);
});
});
await core.group(`Docker images`, async () => {
await exec.exec('docker', ['image', 'ls'], {ignoreReturnCode: true}).catch(error => {
core.info(error);
});
});
if (`${process.env.RUNNER_OS}` == 'Linux') {
await core.group(`Install deps`, async () => {
const sudo = await exec.getExecOutput('which sudo', [], {silent: true, ignoreReturnCode: true})
if (sudo.stdout != "") {
const aptget = await exec.getExecOutput('which apt-get', [], {silent: true, ignoreReturnCode: true})
if (aptget.stdout != "") {
await exec.exec('sudo apt-get update');
await exec.exec('sudo apt-get install -y cgroup-tools cpuid');
} else {
core.info('apt-get not found; not installing deps')
}
} else {
core.info('sudo not found; not installing deps')
}
});
await core.group(`Print cpuinfo`, async () => {
await exec.exec('cat /proc/cpuinfo');
});
await core.group(`Print cpuid`, async () => {
const cpuid = await exec.getExecOutput('which cpuid', [], {silent: true, ignoreReturnCode: true})
if (cpuid.stdout != "") {
await exec.exec('cpuid');
} else {
core.info('cpuid not found')
}
});
await core.group(`File system`, async () => {
await exec.exec('df -ah');
});
await core.group(`Mounts`, async () => {
await exec.exec('mount');
});
await core.group(`Docker daemon conf`, async () => {
if ((fs.statSync('/etc/docker', {throwIfNoEntry: false}) != undefined) &&
(fs.statSync('/etc/docker/daemon.json', {throwIfNoEntry: false}) != undefined)) {
core.info(JSON.stringify(JSON.parse(fs.readFileSync('/etc/docker/daemon.json', {encoding: 'utf-8'}).trim()), null, 2));
} else {
core.info('/etc/docker/daemon.json not present')
}
});
await core.group(`Cgroups`, async () => {
const lscgroup = await exec.getExecOutput('which lscgroup', [], {silent: true, ignoreReturnCode: true})
if (lscgroup.stdout != "") {
await exec.exec('lscgroup');
} else {
core.info('lscgroup not found')
}
});
await core.group(`containerd version`, async () => {
const containerd = await exec.getExecOutput('which containerd', [], {silent: true, ignoreReturnCode: true})
if (containerd.stdout != "") {
await exec.exec('containerd --version');
} else {
core.info('containerd not found')
}
});
}
env:
GHACTION_DCTX_GITHUB_CONTEXT: ${{ toJson(github) }}
GHACTION_DCTX_JOB_CONTEXT: ${{ toJson(job) }}
GHACTION_DCTX_STEPS_CONTEXT: ${{ toJson(steps) }}
GHACTION_DCTX_RUNNER_CONTEXT: ${{ toJson(runner) }}
GHACTION_DCTX_STRATEGY_CONTEXT: ${{ toJson(strategy) }}
GHACTION_DCTX_MATRIX_CONTEXT: ${{ toJson(matrix) }}
41 changes: 24 additions & 17 deletions .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,47 @@ name: CI

on:
push:
branches: [master]
branches: [master,v1]
pull_request:
branches: [master]
branches: [master,v1]
workflow_dispatch:

jobs:
continuous_integration_build:
continue-on-error: true
build:
name: Ruby ${{ matrix.ruby }} on ${{ matrix.operating-system }}
runs-on: ${{ matrix.operating-system }}
continue-on-error: ${{ matrix.experimental == 'Yes' }}
env: { JAVA_OPTS: -Djdk.io.File.enableADS=true }

strategy:
fail-fast: false
matrix:
ruby: [3.0, 3.1, 3.2, 3.3]
# Only the latest versions of JRuby and TruffleRuby are tested
ruby: ["3.0", "3.1", "3.2", "3.3", "truffleruby-23.1.1", "jruby-9.4.5.0"]
operating-system: [ubuntu-latest]
experimental: [No]
include:
- ruby: head
- # Building against head version of Ruby is considered experimental
ruby: head
operating-system: ubuntu-latest
- ruby: truffleruby-23.1.1
operating-system: ubuntu-latest
- ruby: 3.0
operating-system: windows-latest
- ruby: jruby-9.4.5.0
operating-system: windows-latest
experimental: Yes

name: Ruby ${{ matrix.ruby }} on ${{ matrix.operating-system }}

runs-on: ${{ matrix.operating-system }}
- # Only test with minimal Ruby version on Windows
ruby: 3.0
operating-system: windows-latest

env:
JAVA_OPTS: -Djdk.io.File.enableADS=true
- # Since JRuby on Windows is known to not work, consider this experimental
ruby: jruby-9.4.5.0
operating-system: windows-latest
experimental: Yes

steps:
- name: Checkout Code
uses: actions/checkout@v3

- name: Dump context
uses: .github/actions/show-context

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The changes coming in this major release include:
* Update the required Git command line version to at least 2.28
* Update how CLI commands are called to use the [process_executer](https://github.com/main-branch/process_executer)
gem which is built on top of [Kernel.spawn](https://ruby-doc.org/3.3.0/Kernel.html#method-i-spawn).
See [PR #617](https://github.com/ruby-git/ruby-git/pull/617) for more details
See [PR #684](https://github.com/ruby-git/ruby-git/pull/684) for more details
on the motivation for this implementation.

The tentative plan is to release `2.0.0` near the end of March 2024 depending on
Expand Down
180 changes: 180 additions & 0 deletions bin/command_line_test
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'optparse'

# A script used to test calling a command line program from Ruby
#
# This script is used to test the `Git::CommandLine` class. It is called
# from the `test_command_line` unit test.
#
# --stdout: string to output to stdout
# --stderr: string to output to stderr
# --exitstatus: exit status to return (default is zero)
# --signal: uncaught signal to raise (default is not to signal)
#
# Both --stdout and --stderr can be given.
#
# If --signal is given, --exitstatus is ignored.
#
# Examples:
# Output "Hello, world!" to stdout and exit with status 0
# $ bin/command_line_test --stdout="Hello, world!" --exitstatus=0
#
# Output "ERROR: timeout" to stderr and exit with status 1
# $ bin/command_line_test --stderr="ERROR: timeout" --exitstatus=1
#
# Output "Fatal: killed by parent" to stderr and signal 9
# $ bin/command_line_test --stderr="Fatal: killed by parent" --signal=9
#
# Output to both stdout and stderr return default exitstatus 0
# $ bin/command_line_test --stdout="Hello, world!" --stderr="ERROR: timeout"
#


class CommandLineParser
def initialize
@option_parser = OptionParser.new
define_options
end

attr_reader :stdout, :stderr, :exitstatus, :signal

# Parse the command line arguements returning the options
#
# @example
# parser = CommandLineParser.new
# options = parser.parse(['major'])
#
# @param args [Array<String>] the command line arguments
#
# @return [CreateGithubRelease::Options] the options
#
def parse(*args)
begin
option_parser.parse!(remaining_args = args.dup)
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
report_errors(e.message)
end
parse_remaining_args(remaining_args)
# puts options unless options.quiet
# report_errors(*options.errors) unless options.valid?
self
end

private

# @!attribute [rw] option_parser
#
# The option parser
#
# @return [OptionParser] the option parser
#
# @api private
#
attr_reader :option_parser

def define_options
option_parser.banner = "Usage:\n#{command_template}"
option_parser.separator ''
option_parser.separator "Both --stdout and --stderr can be given."
option_parser.separator 'If --signal is given, --exitstatus is ignored.'
option_parser.separator 'If nothing is given, the script will exit with exitstatus 0.'
option_parser.separator ''
option_parser.separator 'Options:'
%i[
define_help_option define_stdout_option define_stderr_option
define_exitstatus_option define_signal_option
].each { |m| send(m) }
end

# The command line template as a string
# @return [String]
# @api private
def command_template
<<~COMMAND
#{File.basename($PROGRAM_NAME)} \
--help | \
[--stdout="string to stdout"] [--stderr="string to stderr"] [--exitstatus=1] [--signal=9]
COMMAND
end

# Define the stdout option
# @return [void]
# @api private
def define_stdout_option
option_parser.on('--stdout="string to stdout"', 'A string to send to stdout') do |string|
@stdout = string
end
end

# Define the stderr option
# @return [void]
# @api private
def define_stderr_option
option_parser.on('--stderr="string to stderr"', 'A string to send to stderr') do |string|
@stderr = string
end
end

# Define the exitstatus option
# @return [void]
# @api private
def define_exitstatus_option
option_parser.on('--exitstatus=1', 'The exitstatus to return') do |exitstatus|
@exitstatus = Integer(exitstatus)
end
end

# Define the signal option
# @return [void]
# @api private
def define_signal_option
option_parser.on('--signal=9', 'The signal to raise') do |signal|
@signal = Integer(signal)
end
end

# Define the help option
# @return [void]
# @api private
def define_help_option
option_parser.on_tail('-h', '--help', 'Show this message') do
puts option_parser
exit 0
end
end

# An error message constructed from the given errors array
# @return [String]
# @api private
def error_message(errors)
<<~MESSAGE
#{errors.map { |e| "ERROR: #{e}" }.join("\n")}
Use --help for usage
MESSAGE
end

# Output an error message and useage to stderr and exit
# @return [void]
# @api private
def report_errors(*errors)
warn error_message(errors)
exit 1
end

# Parse non-option arguments (there are none for this parser)
# @return [void]
# @api private
def parse_remaining_args(remaining_args)
report_errors('Too many args') unless remaining_args.empty?
end
end

options = CommandLineParser.new.parse(*ARGV)

STDOUT.puts options.stdout if options.stdout
STDERR.puts options.stderr if options.stderr
Process.kill(options.signal, Process.pid) if options.signal
exit(options.exitstatus) if options.exitstatus
1 change: 1 addition & 0 deletions git.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Gem::Specification.new do |s|
s.requirements = ['git 2.28.0 or greater']

s.add_runtime_dependency 'addressable', '~> 2.8'
s.add_runtime_dependency 'process_executer', '~> 0.7'
s.add_runtime_dependency 'rchardet', '~> 1.8'

s.add_development_dependency 'minitar', '~> 0.9'
Expand Down

0 comments on commit 17f49cc

Please sign in to comment.