Class: Rubycc::Rmake::Executor
- Inherits:
-
Object
- Object
- Rubycc::Rmake::Executor
- Defined in:
- lib/rubycc/rmake/executor.rb
Overview
Runs an execution Plan (from Makefile#plan) without a shell. The minimal
target environment has no /bin/sh (DESIGN R5), so each recipe line is
interpreted here directly: split into words (honouring quotes), joined by
the connectors make's recipes use (&&, ||, ;), with >/>>/2>
redirections and VAR=value / cd prefixes applied to the command they
front. The vocabulary is fixed by the mkmf corpus (test/fixtures/mkmf): only
the constructs those Makefiles actually emit are accepted, and anything else
raises UnsupportedRecipeError instead of being run through a shell that does
not exist.
The utilities the recipes invoke (rm, mkdir, install, echo, ...) are
reimplemented on top of FileUtils — the "no external tool" implementation is
authoritative, so /usr/bin/install and a bare install behave identically
and no process is spawned for them. Everything the runner does not recognise
as a builtin (the compiler and linker, $(CC)/$(LDSHARED)) is exec'd
directly with an argv array; the runner never builds a shell command string.
B3 adds two things on top of that. First, in-process tool substitution: when
a set of program names is passed as tools, a command whose argv is one
of them (the first word of $(CC)/$(LDSHARED)) is not exec'd but run by
rubycc's own Driver — inside a forked child, so a compiler crash cannot take
rmake down and the Driver's per-invocation state stays isolated. Second, a
-j scheduler that forks independent stale steps up to jobs at a time,
honouring the plan's dependency edges and buffering each worker's output to
flush it whole when the step finishes (make -O's un-interleaved output).
Defined Under Namespace
Classes: LineState, Redirection, SimpleCommand
Constant Summary collapse
- BUILTINS =
The utilities reimplemented in-process, keyed by the command's basename so that
/usr/bin/mkdirandmkdirresolve to the same builtin.:is make's $(NULLCMD);exitis how mkmf'sTOUCH = exit >stamps a timestamp file (the>creates it,exitsucceeds). %w[cd rm mkdir rmdir cp install echo touch true : exit].freeze
Instance Method Summary collapse
-
#execute(plan) ⇒ Object
Run every step of
plan. -
#initialize(dir:, out: $stdout, err: $stderr, dry_run: false, env: ENV, tools: [], jobs: 1) ⇒ Executor
constructor
A new instance of Executor.
Constructor Details
#initialize(dir:, out: $stdout, err: $stderr, dry_run: false, env: ENV, tools: [], jobs: 1) ⇒ Executor
Returns a new instance of Executor.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/rubycc/rmake/executor.rb', line 62 def initialize(dir:, out: $stdout, err: $stderr, dry_run: false, env: ENV, tools: [], jobs: 1) @dir = File.(dir) @out = out @err = err @dry_run = dry_run @env = env @tools = Array(tools) @jobs = [jobs.to_i, 1].max # In sequential mode a substituted tool is fork-isolated for its own sake; # a parallel worker is already a forked step child, so it runs the Driver # in-process rather than forking a second time. @isolate_tool = true end |
Instance Method Details
#execute(plan) ⇒ Object
Run every step of plan. With jobs == 1 (or under -n) this is a
straight sequential walk: prerequisites already precede their dependents
in the plan, so the order is a valid build order. With jobs > 1 the
steps are dispatched by the parallel scheduler instead. Returns the plan;
raises CommandFailedError / UnsupportedRecipeError at the first command
that fails (and is not --prefixed) or cannot be interpreted.
83 84 85 86 87 88 89 90 91 92 |
# File 'lib/rubycc/rmake/executor.rb', line 83 def execute(plan) if @jobs > 1 && !@dry_run execute_parallel(plan) else plan.steps.each do |step| step.commands.each { |command| run_line(step.target, command) } end end plan end |