Class: Rubycc::Rmake::Makefile

Inherits:
Object
  • Object
show all
Defined in:
lib/rubycc/rmake/makefile.rb

Overview

The public entry point: parse Makefile text, then answer "what would make do?" for a goal as an execution Plan. It resolves the raw rules the Parser produced into a working graph — a per-target index of explicit prerequisites and recipes, the .c.o: suffix rules, the .PHONY/ .SUFFIXES special targets and VPATH — and walks that graph in dependency order, comparing timestamps to decide which targets are stale. It only plans: recipes are expanded and their @/-/+ prefixes interpreted, but nothing is run (the runner is B2).

Constant Summary collapse

SUFFIX_TARGET =

A target that names its own inference: .c.o builds .o from .c. Two dots and nothing else, which is how it stays distinct from the one-dot special targets .PHONY / .SUFFIXES.

/\A(\.[^.\s]+)(\.[^.\s]+)\z/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(variables, rules, dir: ".") ⇒ Makefile

Returns a new instance of Makefile.



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/rubycc/rmake/makefile.rb', line 38

def initialize(variables, rules, dir: ".")
  @variables = variables
  @dir = dir
  @expander = Expander.new(@variables)
  @explicit = {}       # target name => {prereqs:, recipe:, double_colon:}
  @suffix_rules = []   # SuffixRule
  @phony = []
  @suffixes = []
  @default_goal = nil
  index_rules(rules)
end

Instance Attribute Details

#default_goalObject (readonly)

Returns the value of attribute default_goal.



25
26
27
# File 'lib/rubycc/rmake/makefile.rb', line 25

def default_goal
  @default_goal
end

#dirObject (readonly)

Returns the value of attribute dir.



25
26
27
# File 'lib/rubycc/rmake/makefile.rb', line 25

def dir
  @dir
end

#phonyObject (readonly)

Returns the value of attribute phony.



25
26
27
# File 'lib/rubycc/rmake/makefile.rb', line 25

def phony
  @phony
end

#suffixesObject (readonly)

Returns the value of attribute suffixes.



25
26
27
# File 'lib/rubycc/rmake/makefile.rb', line 25

def suffixes
  @suffixes
end

#variablesObject (readonly)

Returns the value of attribute variables.



25
26
27
# File 'lib/rubycc/rmake/makefile.rb', line 25

def variables
  @variables
end

Class Method Details

.parse(text, dir: ".", overrides: {}, defaults: {}) ⇒ Object

overrides are command-line variable definitions (make's VAR=value operands) that take precedence over the Makefile's own assignments; defaults are POSIX's built-in variables (currently just MAKE, seeded by CLI). Both are threaded to the Parser, which seeds them — protecting overrides from any Makefile assignment, but leaving defaults as ordinary variables a Makefile assignment can replace.



33
34
35
36
# File 'lib/rubycc/rmake/makefile.rb', line 33

def self.parse(text, dir: ".", overrides: {}, defaults: {})
  parsed = Parser.parse(text, overrides: overrides, defaults: defaults)
  new(parsed.variables, parsed.rules, dir: dir)
end

Instance Method Details

#plan(goal = nil, now: Time.now) ⇒ Object

Compute the execution plan for goal (the default goal when omitted), judging staleness against the filesystem as of now is implicit in the file mtimes read from #dir. Returns a Plan of steps in the order make would run them.

Raises:



54
55
56
57
58
59
60
61
62
63
# File 'lib/rubycc/rmake/makefile.rb', line 54

def plan(goal = nil, now: Time.now)
  @now = now
  goal ||= @default_goal
  raise RmakeError, "no target specified and the Makefile has no default goal" if goal.nil?

  @steps = []
  @state = {}
  build(goal)
  Plan.new(@steps)
end

#run(goal = nil, out: $stdout, err: $stderr, dry_run: false, env: ENV, now: Time.now, tools: nil, jobs: 1) ⇒ Object

Plan goal and then run it through the shell-less Executor (B2/B3). This is the "make it" entry point that pairs with #plan (the "what would make do" entry point). dry_run prints the recipe lines without running them (make -n) and matches #plan's #command_lines.

tools turns on in-process tool substitution (B3): when truthy the recipe commands whose argv is this Makefile's compiler/linker program (the first word of $(CC) / $(LDSHARED), normally "gcc") are run by rubycc's own Driver in a forked child instead of being exec'd, so no external compiler is needed. It defaults off, leaving the B2 behaviour (every command exec'd) exactly as it was. jobs is the maximum number of independent steps to build concurrently (-j; default 1 = sequential).

Returns the Plan that was executed; raises CommandFailedError / UnsupportedRecipeError on the first failing or uninterpretable recipe line.



80
81
82
83
84
85
86
# File 'lib/rubycc/rmake/makefile.rb', line 80

def run(goal = nil, out: $stdout, err: $stderr, dry_run: false, env: ENV, now: Time.now,
        tools: nil, jobs: 1)
  computed = plan(goal, now: now)
  Executor.new(dir: @dir, out: out, err: err, dry_run: dry_run, env: env,
               tools: tools ? tool_programs : [], jobs: jobs).execute(computed)
  computed
end

#tool_programsObject

The set of program names a -tools run substitutes for rubycc: the first word of $(CC) and of $(LDSHARED) (the compile and shared-link drivers mkmf emits). $(LDSHARED) is normally $(CC) -shared, so its flag words already sit inside the expanded recipe and only the leading program name is matched here; for the mkmf corpus both reduce to "gcc".



93
94
95
96
97
# File 'lib/rubycc/rmake/makefile.rb', line 93

def tool_programs
  [variable_value("CC"), variable_value("LDSHARED")]
    .map { |value| value.split(/\s+/).reject(&:empty?).first }
    .compact.uniq
end

#variable_value(name) ⇒ Object

The fully-expanded value of a variable (empty string when undefined).



100
101
102
# File 'lib/rubycc/rmake/makefile.rb', line 100

def variable_value(name)
  @expander.expand("$(#{name})")
end