bwrap 1.0.0.pre.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/version"
4
+ require_relative "execution"
5
+
6
+ # Exit codes for exit status handling.
7
+ #
8
+ # These are not strictly speaking related to execution system,
9
+ # but rather to handle execution status of actual scripts.
10
+ # For example, to set exit code for {Bwrap::Output#error} call.
11
+ #
12
+ # Since this is fully own module, it shouldn’t harm for this to be here.
13
+ module Bwrap::Execution::Labels
14
+ # If we had a successful run, i.e. no error happened.
15
+ NO_ERROR = 0
16
+
17
+ # Do not use.
18
+ INVALID_ERROR = 1
19
+
20
+ # When Output#error has been called with no :label.
21
+ UNSPECIFIED_ERROR = 2
22
+
23
+ # If no better error code has been created.
24
+ GENERIC_ERROR = 3
25
+
26
+ # Converts given label to real exit code.
27
+ #
28
+ # @param label [Symbol] Label to be converted to a exit code
29
+ #
30
+ # Accepted labels:
31
+ # - :success
32
+ # - :unspecified
33
+ # - :generic
34
+ # - :target_host_not_found
35
+ # - :no_package_manager
36
+ # - :debootstrap_failed
37
+ # - :file_not_modified
38
+ #
39
+ # Additional labels can be added with {.add_label}.
40
+ #
41
+ # @return [Integer] Exit code between 0 to 255
42
+ def self.resolve_exit_code label
43
+ return @additional_labels[label] if @additional_labels and @additional_labels[label]
44
+
45
+ case label
46
+ when :success
47
+ NO_ERROR
48
+ when :unspecified
49
+ UNSPECIFIED_ERROR
50
+ when :generic
51
+ GENERIC_ERROR
52
+ else
53
+ warn "Got invalid error label: #{label}"
54
+ INVALID_ERROR
55
+ end
56
+ end
57
+
58
+ # Add a custom label.
59
+ #
60
+ # @param label [Symbol] Name of exit code label
61
+ # @param exit_code [Integer] Number in range 0-255
62
+ def self.add_label label, exit_code
63
+ @additional_labels ||= {}
64
+ @additional_labels[label] = exit_code
65
+ end
66
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/version"
4
+ require "bwrap/output"
5
+ require_relative "execution/execute"
6
+
7
+ # @abstract Module to be included in a class that needs to execute commands.
8
+ #
9
+ # Methods to execute processes.
10
+ module Bwrap::Execution
11
+ include Bwrap::Output
12
+
13
+ # Unspecified execution related error.
14
+ class CommandError < StandardError
15
+ end
16
+
17
+ # Signifies that command execution has failed.
18
+ class ExecutionFailed < CommandError
19
+ end
20
+
21
+ # Actual implementation of execution command. Can be used when static method is needed.
22
+ #
23
+ # @note When an array is given as a command, empty strings are passed as empty arguments.
24
+ #
25
+ # This means that `[ "foo", "bar" ]` passes one argument to "foo" command, when
26
+ # `[ "foo", "", "bar" ]` passes two arguments.
27
+ #
28
+ # This may or may not be what is assumed, so it can’t be fixed here. It is up to the
29
+ # command to decide how to handle empty arguments.
30
+ #
31
+ # Returns pid of executed command if wait is false.
32
+ # Returns command output if wait is true.
33
+ #
34
+ # fail == If true, an error is raised in case the command returns failure code.
35
+ #
36
+ # @param error if :show, warn()s output of the command is shown if execution failed.
37
+ #
38
+ # @see #execute
39
+ def self.do_execute command,
40
+ fail: true,
41
+ wait: true,
42
+ log: true,
43
+ direct_output: false,
44
+ env: {},
45
+ clear_env: false,
46
+ error: nil,
47
+ log_callback: 2,
48
+ rootcmd: nil
49
+ command = Execute.format_command command, rootcmd: rootcmd
50
+ Execute.handle_logging command, log_callback: log_callback, log: log, dry_run: @dry_run
51
+ return if @dry_run || Bwrap::Execution::Execute.dry_run
52
+
53
+ Execute.open_pipes direct_output
54
+
55
+ # If command is an array, there can’t be arrays inside the array.
56
+ # For convenience, the array is flattened here, so callers can construct commands more easily.
57
+ if command.respond_to? :flatten!
58
+ command.flatten!
59
+ end
60
+
61
+ # If command is string, splat operator (the *) does not do anything. If array, it expand the arguments.
62
+ # This causes spawning work correctly, as that’s how spawn() expects to have the argu
63
+ pid = spawn(env, *command, err: [ :child, :out ], out: Execute.w, unsetenv_others: clear_env)
64
+ output = Execute.finish_execution(log: log, wait: wait, direct_output: direct_output)
65
+ return pid unless wait
66
+
67
+ # This is instant return, but allows us to have $?/$CHILD_STATUS set.
68
+ Process.wait pid
69
+ @last_status = $CHILD_STATUS
70
+
71
+ output = Execute.process_output output: output
72
+ Execute.handle_execution_fail fail: fail, error: error, output: output
73
+ output
74
+ ensure
75
+ Execute.clean_variables
76
+ end
77
+
78
+ # Returns Process::Status instance of last execution.
79
+ #
80
+ # @note This only is able to return the status if wait is true, as otherwise caller is assumed to
81
+ # handle execution flow.
82
+ def self.last_status
83
+ @last_status
84
+ end
85
+
86
+ # Check if requested program can be found.
87
+ #
88
+ # Should work cross-platform and in restricted environents pretty well.
89
+ private def command_available? command
90
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
91
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
92
+ exts.each do |ext|
93
+ exe = File.join(path, "#{command}#{ext}")
94
+ return true if File.executable?(exe) && !File.directory?(exe)
95
+ end
96
+ end
97
+ false
98
+ end
99
+
100
+ # Returns path to given executable.
101
+ private def which command, fail: true
102
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
103
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
104
+ exts.each do |ext|
105
+ exe = File.join(path, "#{command}#{ext}")
106
+ return exe if File.executable?(exe) && !File.directory?(exe)
107
+ end
108
+ end
109
+ error "Failed to find #{command} from PATH." if fail
110
+ nil
111
+ end
112
+
113
+ # Execute a command.
114
+ #
115
+ # This method can be used by including Execution module in a class that should be able to
116
+ # execute commands.
117
+ #
118
+ # @see .do_execute .do_execute for documentation of argument syntax
119
+ private def execute *args
120
+ # Mangle proper location to error message.
121
+ if args.last.is_a? Hash
122
+ args.last[:log_callback] = 3
123
+ else
124
+ args << { log_callback: 3 }
125
+ end
126
+ Bwrap::Execution.do_execute(*args)
127
+ end
128
+
129
+ # Same as ::execute, but uses log: false to avoid unnecessary output when we’re just getting a
130
+ # value for internal needs.
131
+ #
132
+ # Defaults to fail: false, since when one just wants to get the value, there is not that much
133
+ # need to unconditionally die if getting bad exit code.
134
+ private def execvalue *args, fail: false, rootcmd: nil, env: {}
135
+ # This logging handling is a bit of duplication from execute(), but to be extra safe, it is duplicated.
136
+ # The debug message contents will always be evaluated, so can just do it like this.
137
+ log_command = args[0].respond_to?(:join) && args[0].join(" ") || args[0]
138
+ log_command =
139
+ if log_command.frozen?
140
+ log_command.dup.force_encoding("UTF-8")
141
+ else
142
+ log_command.force_encoding("UTF-8")
143
+ end
144
+ if @dry_run
145
+ puts "Would execvalue “#{log_command}” at #{caller_locations(1, 1)[0]}"
146
+ return
147
+ end
148
+ trace "Execvaluing “#{log_command}” at #{caller_locations(1, 1)[0]}"
149
+ execute(*args, fail: fail, log: false, rootcmd: rootcmd, env: env)
150
+ end
151
+
152
+ private def exec_success?
153
+ $CHILD_STATUS.success?
154
+ end
155
+
156
+ private def exec_failure?
157
+ !exec_success?
158
+ end
159
+
160
+ # When running through bundler, don’t use whatever it defines as we’re running inside chroot.
161
+ private def clean_execute
162
+ if (Bundler&.bundler_major_version) >= 2
163
+ Bundler.with_unbundled_env do
164
+ yield 2
165
+ end
166
+ elsif Bundler&.bundler_major_version == 1
167
+ Bundler.with_clean_env do
168
+ yield 1
169
+ end
170
+ else
171
+ yield nil
172
+ end
173
+ rescue NameError
174
+ # If NameError is thrown, no Bundler is available.
175
+ yield nil
176
+ end
177
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "output"
4
+
5
+ # Methods to color CLI output.
6
+ module Bwrap::Output::Colors
7
+ # Outputs ANSI true color sequence.
8
+ def self.color red, green, blue, bold: false
9
+ return unless $stdout.tty?
10
+ return if ENV["COLORTERM"] != "truecolor" && ENV["COLORTERM"] != "24bit"
11
+
12
+ out = ""
13
+ out += "\x1b[38;1;1m" if bold
14
+ # https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
15
+ out += "\x1b[38;2;#{red};#{green};#{blue}m"
16
+
17
+ out
18
+ end
19
+
20
+ # Outputs ANSI command to stop color output.
21
+ def self.stopcolor
22
+ return unless $stdout.tty?
23
+ return if ENV["COLORTERM"] != "truecolor" && ENV["COLORTERM"] != "24bit"
24
+
25
+ "\x1b[0m"
26
+ end
27
+
28
+ # Calls `Bwrap::Output::Colors#color`.
29
+ private def color red, green, blue, bold: false
30
+ Bwrap::Output::Colors.color red, green, blue, bold
31
+ end
32
+
33
+ # Calls `Bwrap::Output::Colors#stopcolor`.
34
+ private def stopcolor
35
+ Bwrap::Output::Colors.stopcolor
36
+ end
37
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "colors"
4
+
5
+ # Output level functionality.
6
+ class Bwrap::Output::Levels
7
+ include Bwrap::Output::Colors
8
+
9
+ @@_verbose = false
10
+ @@_debug = false
11
+ @@_trace = ENV["BWRAP_TRACE"] && true || false
12
+
13
+ # @see Bwrap::Output#verbose?
14
+ def self.verbose?
15
+ @@_verbose
16
+ end
17
+
18
+ # @see Bwrap::Output##debug?
19
+ def self.debug?
20
+ @@_debug
21
+ end
22
+
23
+ # @see Bwrap::Output##trace?
24
+ def self.trace?
25
+ @@_trace
26
+ end
27
+
28
+ # Takes hash of options received from Optimist and checks output related flags.
29
+ def self.handle_output_options options
30
+ # Set output level flags to true or false, if it was given.
31
+ unless options[:trace].nil?
32
+ @@_verbose = options[:trace]
33
+ @@_debug = options[:trace]
34
+ @@_trace = options[:trace]
35
+ end
36
+ unless options[:debug].nil?
37
+ @@_verbose = options[:debug]
38
+ @@_debug = options[:debug]
39
+ end
40
+ unless options[:verbose].nil?
41
+ @@_verbose = options[:verbose]
42
+ end
43
+ end
44
+
45
+ # Formats given string and outputs it.
46
+ #
47
+ # @return formatted string
48
+ def self.trace_print_formatted str, log_callback: 1
49
+ out = "#{Bwrap::Output::Colors.color(230, 165, 240)}[TRACE]#{Bwrap::Output::Colors.stopcolor} #{str}"
50
+ out = append_caller out, log_callback: (log_callback + 1)
51
+ puts out
52
+
53
+ out
54
+ end
55
+
56
+ # Formats given string and outputs it.
57
+ #
58
+ # @return formatted string
59
+ def self.debug_print_formatted str, log_callback: 1
60
+ out = "#{Bwrap::Output::Colors.color(190, 190, 190)}[DEBUG]#{Bwrap::Output::Colors.stopcolor} #{str}"
61
+ out = append_caller out, log_callback: (log_callback + 1)
62
+ puts out
63
+
64
+ out
65
+ end
66
+
67
+ # Formats given string and outputs it.
68
+ #
69
+ # @return formatted string
70
+ def self.verbose_print_formatted str, log_callback: 1
71
+ out = "#{Bwrap::Output::Colors.color(130, 230, 130, bold: true)}[INFO]#{Bwrap::Output::Colors.stopcolor} #{str}"
72
+ out = append_caller out, log_callback: (log_callback + 1)
73
+ puts out
74
+
75
+ out
76
+ end
77
+
78
+ # Formats given string and outputs it.
79
+ #
80
+ # @return formatted string
81
+ def self.warning_print_formatted str, log_callback: 1
82
+ out = "#{Bwrap::Output::Colors.color(190, 190, 110, bold: true)}[WARN]#{Bwrap::Output::Colors.stopcolor} #{str}"
83
+ out = "#{out} (called at #{caller_locations(log_callback, 1)[0]})" if @@_debug or @@_trace
84
+ $stderr.puts out
85
+
86
+ out
87
+ end
88
+
89
+ # Formats given string and outputs it.
90
+ def self.error_print_formatted str, log_callback: 1
91
+ out = "#{Bwrap::Output::Colors.color(230, 110, 110, bold: true)}[ERROR]#{Bwrap::Output::Colors.stopcolor} #{str}"
92
+ out = "#{out} (called at #{caller_locations(log_callback, 1)[0]})" if @@_debug or @@_trace
93
+ $stderr.puts out
94
+ end
95
+
96
+ # Private class method.
97
+ #
98
+ # Appends caller information to given output.
99
+ #
100
+ # Used by *_print_formatted methods.
101
+ def self.append_caller out, log_callback: 1
102
+ out = "#{out} (called at #{caller_locations(log_callback, 1)[0]})" if @@_trace
103
+ out
104
+ end
105
+ private_class_method :append_caller
106
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: false
2
+
3
+ # force_encoding modifies string, so can’t freeze strings.
4
+
5
+ require_relative "output"
6
+
7
+ # Logging methods.
8
+ class Bwrap::Output::Log
9
+ @@log_file = nil
10
+
11
+ # Get handle of opened log file.
12
+ def self.log_file
13
+ @@log_file
14
+ end
15
+
16
+ # Writes given string to log.
17
+ def self.write_to_log str
18
+ @@log_file&.write str.force_encoding("UTF-8")
19
+ end
20
+
21
+ # Writes given string to log.
22
+ def self.puts_to_log str
23
+ @@log_file&.puts str.force_encoding("UTF-8")
24
+ end
25
+
26
+ # Closes an open log file.
27
+ def self.close_log_file
28
+ @@log_file&.close
29
+ @@log_file = nil
30
+ end
31
+
32
+ # Starts logging to given file.
33
+ def self.log_to_file log_path
34
+ log_file = File.open log_path, "w"
35
+
36
+ # In default mode, log messages disappears as Ruby’s own buffer gets full.
37
+ # This makes only OS buffer being used.
38
+ log_file.sync = false
39
+
40
+ @@log_file = log_file
41
+
42
+ at_exit do
43
+ ::Bwrap::Output::Log.close_log_file
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/version"
4
+
5
+ # Outputting utilities.
6
+ module Bwrap::Output
7
+ # Nya.
8
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Have variables like $CHILD_STATUS which is alias of $?.
4
+ require "English"
5
+
6
+ require "bwrap/execution/labels"
7
+ require_relative "output/colors"
8
+ require_relative "output/levels"
9
+ require_relative "output/log"
10
+
11
+ # Extends the module with outputting methods.
12
+ module Bwrap::Output
13
+ include Bwrap::Output::Colors
14
+
15
+ # @see #verbose?
16
+ def self.verbose?
17
+ Bwrap::Output::Levels.verbose?
18
+ end
19
+
20
+ # @see #debug?
21
+ def self.debug?
22
+ Bwrap::Output::Levels.debug?
23
+ end
24
+
25
+ # @see #trace?
26
+ def self.trace?
27
+ Bwrap::Output::Levels.trace?
28
+ end
29
+
30
+ # Takes hash of options received from Optimist and checks output related flags.
31
+ def self.handle_output_options options
32
+ Bwrap::Output::Levels.handle_output_options options
33
+ end
34
+
35
+ # Handler used by #trace to output given string.
36
+ def self.trace_output str, raw: false, log_callback: 1
37
+ return unless trace?
38
+
39
+ if raw
40
+ print str
41
+ else
42
+ out = Bwrap::Output::Levels.trace_print_formatted str, log_callback: (log_callback + 1)
43
+ end
44
+ Bwrap::Output::Log.puts_to_log out || str
45
+ end
46
+
47
+ # Handler used by #debug to output given string.
48
+ def self.debug_output str, raw: false, log_callback: 1
49
+ return unless debug?
50
+
51
+ if raw
52
+ print str
53
+ else
54
+ out = Bwrap::Output::Levels.debug_print_formatted str, log_callback: (log_callback + 1)
55
+ end
56
+ Bwrap::Output::Log.puts_to_log out || str
57
+ end
58
+
59
+ # Handler used by #verb to output given string.
60
+ def self.verb_output str, raw: false, log_callback: 1
61
+ return unless verbose?
62
+
63
+ if raw
64
+ print str
65
+ else
66
+ out = Bwrap::Output::Levels.verbose_print_formatted str, log_callback: (log_callback + 1)
67
+ end
68
+ Bwrap::Output::Log.puts_to_log out || str
69
+ end
70
+
71
+ # Handler used by #warn to output given string.
72
+ def self.warn_output str, raw: false, log_callback: 1
73
+ if raw
74
+ print str
75
+ else
76
+ out = Bwrap::Output::Levels.warning_print_formatted str, log_callback: (log_callback + 1)
77
+ end
78
+ Bwrap::Output::Log.puts_to_log out || str
79
+ end
80
+
81
+ # Aborts current process.
82
+ #
83
+ # Use this instead of Ruby’s #exit in order to filter out dummy #exit calls from the code.
84
+ def self.error_output str = nil, label: :unspecified, log_callback: 1
85
+ unless str.nil?
86
+ out = Bwrap::Output::Levels.error_print_formatted str, log_callback: (log_callback + 1)
87
+ Bwrap::Output::Log.puts_to_log out
88
+ end
89
+
90
+ exit Bwrap::Execution::Labels.resolve_exit_code(label)
91
+ end
92
+
93
+ # @return true if --verbose, --debug or --trace has been passed, false if not.
94
+ private def verbose?
95
+ Bwrap::Output::Levels.verbose?
96
+ end
97
+
98
+ # @return true if --debug or --trace has been passed, false if not.
99
+ private def debug?
100
+ Bwrap::Output::Levels.debug?
101
+ end
102
+
103
+ # @return true if --trace has been passed, false if not.
104
+ private def trace?
105
+ Bwrap::Output::Levels.trace?
106
+ end
107
+
108
+ # Outputs given string if trace flag has been set.
109
+ #
110
+ # Output flags can be set with {.handle_output_options}.
111
+ #
112
+ # @param str String to be outputted
113
+ # @param raw [Boolean] If true, disables output formatting
114
+ private def trace str, raw: false
115
+ Bwrap::Output.trace_output(str, raw: raw, log_callback: 2)
116
+ end
117
+
118
+ # Outputs given string if debug flag has been set.
119
+ #
120
+ # Output flags can be set with {.handle_output_options}.
121
+ #
122
+ # @param str String to be outputted
123
+ # @param raw [Boolean] If true, disables output formatting
124
+ private def debug str, raw: false
125
+ Bwrap::Output.debug_output(str, raw: raw, log_callback: 2)
126
+ end
127
+
128
+ # Outputs given string if verbose flag has been set.
129
+ #
130
+ # Output flags can be set with {.handle_output_options}.
131
+ #
132
+ # @param str String to be outputted
133
+ # @param raw [Boolean] If true, disables output formatting
134
+ private def verb str, raw: false
135
+ Bwrap::Output.verb_output(str, raw: raw, log_callback: 2)
136
+ end
137
+
138
+ # Outputs given string to `$stderr`.
139
+ #
140
+ # @param str String to be outputted
141
+ # @param raw [Boolean] If true, disables output formatting
142
+ private def warn str, raw: false
143
+ Bwrap::Output.warn_output(str, raw: raw, log_callback: 2)
144
+ end
145
+
146
+ # Outputs given string to `$stderr` and halts execution.
147
+ #
148
+ # @param str String to be outputted
149
+ # @param label [Symbol] Exit label accepted by {Bwrap::Execution.resolve_exit_code}
150
+ private def error str = nil, label: :unspecified
151
+ Bwrap::Output.error_output(str, label: label, log_callback: 2)
152
+ end
153
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # bwrap command runner tools.
4
+ module Bwrap
5
+ # Current version of bwrap.
6
+ VERSION = "1.0.0-alpha1"
7
+ end
8
+
9
+ require "deep-cover" if ENV["DEEP_COVER"]
data/lib/bwrap.rb ADDED
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optimist"
4
+
5
+ #require "deep-cover" if ENV["DEEP_COVER"]
6
+
7
+ require "bwrap/version"
8
+ require "bwrap/args/construct"
9
+ require "bwrap/config"
10
+ require "bwrap/execution"
11
+
12
+ # Executes bwrap command using given configuration.
13
+ class Bwrap::Bwrap
14
+ include Bwrap::Execution
15
+
16
+ def initialize config
17
+ @config = config
18
+
19
+ parse_command_line_arguments
20
+ end
21
+
22
+ # Runs given command inside bwrap.
23
+ #
24
+ # @param command [String, Array] Command, with necessary arguments, to be executed inside bwrap
25
+ def run command
26
+ construct = Bwrap::Args::Construct.new
27
+ construct.config = @config
28
+ bwrap_args = construct.construct_bwrap_args
29
+
30
+ exec_command = [ "bwrap" ]
31
+ exec_command += bwrap_args
32
+ exec_command += %W{ #{command} --new-instance }
33
+ exec_command += ARGV unless ARGV.empty?
34
+
35
+ execute exec_command
36
+
37
+ construct.cleanup
38
+ end
39
+
40
+ # Parses command line arguments given to caller script.
41
+ private def parse_command_line_arguments
42
+ options = optimist_cli_args
43
+
44
+ Bwrap::Output.handle_output_options options
45
+ end
46
+
47
+ # Parses global bwrap flags using Optimist.
48
+ private def optimist_cli_args
49
+ Optimist.options do
50
+ version ::Bwrap::VERSION
51
+
52
+ banner "Usage:"
53
+ banner " #{$PROGRAM_NAME} [global options]\n \n"
54
+ banner "Global options:"
55
+ opt :verbose,
56
+ "Show verbose output",
57
+ short: "v"
58
+ opt :debug,
59
+ "Show debug output (useful when debugging a problem)",
60
+ short: "d"
61
+ opt :trace,
62
+ "Show trace output (noisiest, probably not useful for most of time)",
63
+ short: :none
64
+ opt :version,
65
+ "Print version and exit",
66
+ short: "V"
67
+ opt :help,
68
+ "Show help message",
69
+ short: "h"
70
+
71
+ educate_on_error
72
+ end
73
+ end
74
+ end
data.tar.gz.sig ADDED
Binary file