class Rmk::Generator

Generator for Ninja #files.

Constants

LIBRARIES_FILE_NAME
TOPLEVEL_TARGETS

Attributes

build_prefix[R]
configuration[R]
cwd[R]
default_targets[R]
exclude_patterns[RW]
exclude_subproject_patterns[RW]
files[R]
hostname[R]
intermediate_targets[R]
lib_base_name[R]
main_object_files[R]
ninja[R]
object_files[R]
priv_file[R]
proj_base[R]
proj_file[R]
proj_root[R]
rmakefile_deps[R]
rmk_command[RW]
subproject_options[R]
subproject_paths[R]
toplevel_targets[R]

Public Class Methods

new(options) click to toggle source

Create ::new generator (singleton) instance. Parameters in options:

:config

#configuration, one of Rmk#configurations, use Rmk#default_configuration if not provided

:rmk_config

rmk command used as generator (optional)

:build_only

partial initialization, generator is used only to spawn ninja process

# File lib/generator.rb, line 62
def initialize(options)
  raise 'can only have one instance' if @@generator
  @@generator = self

  @@log.outputters ||= Outputter.stderr

  @rmk_command = options[:rmk_command]
  @configuration = check_configuration(options[:config] || :default)

  @cwd = Pathname.new(Dir.pwd)

  @proj_file = nil
  @priv_files = []

  @files = []
  @object_files = []
  @main_object_files = {}

  @toplevel_targets = {}
  @intermediate_targets = {}

  @rmakefile_deps = []

  @subproject_paths = []
  @subproject_options = {}
  @exclude_subproject_patterns = []

  @exclude_patterns = [/~$/, /^\./, /^#/, /^core/, /[0-9]+$/]

  @this_proj_dir = @cwd.clone
  @proj_base = @this_proj_dir.basename.to_s.tr(' ', '_')

  @hostname = `uname -n`.chop

  define_build_prefix!
  find_project_root!

  define_system_file_extensions!
  define_lib_base_name!

  if options[:build_only]
    @@log.debug "build only, don't generate: skip initialization"

    return # stop initialization here
  end

  read_init_files!

  if DEBUG_TRUE
    @@log.debug "configuration = #{configuration}"
    @@log.debug "cwd = #{@cwd}"
    @@log.debug "project directory = '#{@this_proj_dir}'"
    @@log.debug "project root = '#{@proj_root}'"
    @@log.debug "project base = '#{@proj_base}'"
    @@log.debug "system file extensions = #{@ext.inspect}"
    @@log.debug "build prefix = '#{@build_prefix}'"
    @@log.debug "library base name = '#{@lib_base_name}'"
  end

  read_directory

  # @@log.debug "files = '#{@files.join(' ')}'"

  @ninja = nil
end

Public Instance Methods

add_file(name) click to toggle source
# File lib/generator.rb, line 258
def add_file(name)
  @files << name
  name
end
add_object(name, main = false) click to toggle source

Add #object file.

  • name file name

  • main set if this file is not part of the project library.

main can have values

# File lib/generator.rb, line 282
def add_object(name, main = false)
  @object_files << name unless @object_files.include?(name) # TODO: 'include?' slow; e.g., cc and mex may add
  if main
    if !@main_object_files[name]
      @main_object_files[name] = main
    else
      @logger.warn "add_object '#{name}': "\
                   "use '#{@main_object_files[name]}, ignore #{main}"
    end
  end
  name
end
add_to_intermediate_target(target, what) click to toggle source
# File lib/generator.rb, line 409
def add_to_intermediate_target(target, what)
  @intermediate_targets[target] ||= []
  Array(what).each do |w|
    unless @intermediate_targets[target].include?(w)
      @intermediate_targets[target] << w
    end
  end
end
add_to_toplevel_target(top, what) click to toggle source
# File lib/generator.rb, line 398
def add_to_toplevel_target(top, what)
  Array(top).each do |t|
    raise "invalid top level target '#{t}'" unless TOPLEVEL_TARGETS.include?(t)
    @toplevel_targets[t] ||= []

    Array(what).each do |w|
      @toplevel_targets[t] << w unless @toplevel_targets[t].include?(w)
    end
  end
end
apply_tools() click to toggle source
# File lib/generator.rb, line 505
def apply_tools
  provide_required_tools
  tool_chain.each do |tool|
    @@log.debug "processing '#{tool.class}'"
    tool.ninja_begin
    tool.ninja_variables
    tool.ninja_rules
    tool.ninja_build
    tool.ninja_end
  end
end
build!() click to toggle source
# File lib/generator.rb, line 483
def build!
  ninja_begin

  use_subprojects

  ninja_variables
  ninja_rules

  subninja

  ninja_build

  apply_tools

  define_intermediate_targets
  define_toplevel_targets

  ninja_end

  store_persistent_data!
end
build_directory(cfg) click to toggle source
# File lib/generator.rb, line 338
def build_directory(cfg)
  @cwd.join(build_directory_name(cfg))
end
build_directory_name(cfg = configuration) click to toggle source
# File lib/generator.rb, line 330
def build_directory_name(cfg = configuration)
  @build_prefix + configuration_suffix(cfg)
end
config_files(mode) click to toggle source
# File lib/generator.rb, line 193
def config_files(mode)
  case mode
  when :global
    path = Pathname.new(File.dirname(__FILE__)).join('../config')
  when :user
    path = Pathname.new(ENV['RMK_LOCAL'] || '~/.rmk').join('config')
  else
    raise "invalid mode '#{mode}'"
  end

  path = [hostname, 'localhost', 'default'].compact.map do |dir|
    File.expand_path(path.join(dir))
  end
  path = first_existing_file(path)

  if path
    ['init.rb', 'packages', 'tools'].map { |d| Pathname.new(path).join(d) }
  else
    []
  end
end
configuration_suffix(cfg = configuration) click to toggle source
# File lib/generator.rb, line 372
def configuration_suffix(cfg = configuration)
  if cfg.nil? || cfg == :default
    ''
  else
    '_' + cfg.to_s
  end
end
create_build_directory(dir) click to toggle source
# File lib/generator.rb, line 342
def create_build_directory(dir)
  unless File.directory?(dir)
    @@log.debug "create build directory '#{dir}'"
    Dir.mkdir(dir)
  end
  dir
end
create_proj_build_directory(proj, cfg = configuration) click to toggle source
# File lib/generator.rb, line 354
def create_proj_build_directory(proj, cfg = configuration)
  create_build_directory(proj_build_directory(proj, cfg))
end
create_this_build_directory() click to toggle source
# File lib/generator.rb, line 350
def create_this_build_directory
  create_build_directory(this_build_directory)
end
define_build_prefix!() click to toggle source
# File lib/generator.rb, line 128
def define_build_prefix!
  @build_prefix = `uname -s`.chop
  raise "'uname' failed" if @build_prefix.empty?
  @build_prefix.tr!(' ', '_')
end
define_intermediate_targets() click to toggle source
# File lib/generator.rb, line 427
def define_intermediate_targets
  ninja.comment 'intermediate targets'

  @intermediate_targets.each do |target, what|
    ninja.phony(wrap_target(target),
                what.map { |w| wrap_target(w) })
  end
end
define_lib_base_name!() click to toggle source
# File lib/generator.rb, line 176
def define_lib_base_name!
  @lib_base_name = @cwd.relative_path_from(Pathname.new(@proj_root)).to_s
                       .gsub(/[ \/]/, '_')
end
define_subproject(*paths) click to toggle source
# File lib/generator.rb, line 661
def define_subproject(*paths)
  stable_unique_paths(paths).map do |p|
    if exclude_subproject?(p)
      logger.warn "exclude '#{p}'"
      next nil
    end

    path = @cwd.join(p)
    unless File.directory?(path)
      @@log.warn "recursive build: ignore nonexistent path '#{path}'"
    end
    unless File.exist?(path.join('RMakefile'))
      @@log.warn "recursive build: ignore '#{path}', no 'RMakefile'"
    end

    @subproject_paths << p
    @subproject_options[p] ||= {}

    p
  end.compact
end
define_system_file_extensions!() click to toggle source
# File lib/generator.rb, line 166
def define_system_file_extensions!
  @ext = {
    exe: '',
    o: '.o',
    so: '.so',
    a: '.a',
    lib_prefix: 'lib'
  }
end
define_toplevel_targets() click to toggle source
# File lib/generator.rb, line 440
def define_toplevel_targets
  ninja.comment 'top level targets'

  @toplevel_targets.each do |target, what|
    if target != :default
      ninja.phony(wrap_target(target),
                  what.map { |w| wrap_target(w) })
    end
  end
  # TODO: default = project_... (phony target instead of explicit list)

  default_targets = wrap_targets_array(@toplevel_targets[:default])
  all_targets = wrap_targets_array(@toplevel_targets[:all])

  if !default_targets.empty?
    ninja.phony("proj_#{lib_base_name}", default_targets)
    ninja.default(default_targets)
  elsif all_targets.empty?
    ninja.default(all_targets)
  end
end
exclude?(name) click to toggle source
# File lib/generator.rb, line 250
def exclude?(name)
  !@exclude_patterns.find { |p| name =~ p }.nil?
end
exclude_subproject?(path) click to toggle source
# File lib/generator.rb, line 657
def exclude_subproject?(path)
  !@exclude_subproject_patterns.find { |p| path =~ p }.nil?
end
exe(name) click to toggle source
# File lib/generator.rb, line 263
def exe(name)
  strip_extension(name) + @ext[:exe]
end
expand_source(src) click to toggle source
# File lib/generator.rb, line 368
def expand_source(src)
  "$srcdir/#{src}"
end
find_project_root!() click to toggle source
# File lib/generator.rb, line 134
def find_project_root!
  @proj_root = recursive_find_project_root(@cwd)

  if !@proj_root
    raise "no 'RMakefile' in '#{@cwd}'" unless File.exist?('RMakefile')
    @proj_root = @this_proj_dir.parent
    @@log.warn 'could not determine project root directory, '\
               "assume '#{@proj_root}'"
  else
    @@log.debug "project root = '#{@proj_root}'"
  end
end
library_file_name(base, type) click to toggle source
# File lib/generator.rb, line 295
def library_file_name(base, type)
  @ext[:lib_prefix] + base + @ext[type]
end
load_rmakefile(file) click to toggle source
# File lib/generator.rb, line 181
def load_rmakefile(file)
  @@log.debug "load '#{file}' in '#{@cwd}'"
  raise "found no '#{file}' in '#{@cwd}'" unless File.exist?(file)
  begin
    load(file)
  rescue StandardError => e
    @@log.error "ERROR while loading file '#{file}' in directory '#{Dir.pwd}'"
    raise e
  end
  @rmakefile_deps << file
end
ninja_begin() click to toggle source
# File lib/generator.rb, line 462
def ninja_begin
  out = ninja_file_path
  @@log.debug "ninja file = '#{out}'"
  os = case out
       when '-' then $stdout
       else
         create_this_build_directory
         File.open(out, 'w+b')
       end
  @ninja = Ninja::Writer.new(os)
  ninja.rule_suffix = lib_base_name
  ninja.comment "created by #{$PROGRAM_NAME}"
  ninja.newline
end
ninja_build(writer = ninja) click to toggle source
# File lib/generator.rb, line 613
def ninja_build(writer = ninja)
  # @@log.info "add more dependencies here" # e.g., all ruby source files

  writer.build(ninja_file_target, 'generate', [],
               implicit: rmakefile_deps.map { |f| File.expand_path(f.to_s) })
  writer.newline
end
ninja_end() click to toggle source
# File lib/generator.rb, line 477
def ninja_end
  @ninja.out.flush
  @ninja = nil
  @@log.debug "wrote ninja file '#{ninja_file_name}'"
end
ninja_file_name(_cfg = configuration) click to toggle source
# File lib/generator.rb, line 380
def ninja_file_name(_cfg = configuration)
  # "build#{configuration_suffix(cfg)}.ninja"
  'build.ninja'
end
ninja_file_path(cfg = configuration) click to toggle source
# File lib/generator.rb, line 391
def ninja_file_path(cfg = configuration)
  # @cwd.join(ninja_file_name(cfg))
  build_directory(cfg).join(ninja_file_name(cfg))
end
ninja_file_target(cfg = configuration) click to toggle source
# File lib/generator.rb, line 385
def ninja_file_target(cfg = configuration)
  # "$srcdir/#{ninja_file_name(cfg)}"
  "$builddir/#{ninja_file_name(cfg)}" # TODO: BUG?
  # "#{build_directory_name}/#{ninja_file_name(cfg)}" -- does not work for subninja!
end
ninja_rules(writer = ninja) click to toggle source
# File lib/generator.rb, line 597
def ninja_rules(writer = ninja)
  # generator
  cmd = rmk_command

  unless cmd
    @@log.warn 'prefer providing rmk command explicitly'
    cmd = "ruby #{$PROGRAM_NAME} #{(ARGV + ['-n']).join(' ')}"
  end

  # TODO: handle paths correctly / change cwd
  writer.rule('generate', cmd,
              description: 'generate $out',
              generator: true)
  writer.newline
end
ninja_variables(writer = ninja) click to toggle source
# File lib/generator.rb, line 589
def ninja_variables(writer = ninja)
  writer.comment 'Build'
  writer.variable('project', lib_base_name)
  writer.variable('srcdir', File.expand_path(@cwd))
  writer.variable('builddir', File.expand_path(this_build_directory))
  writer.newline
end
object(name) click to toggle source
# File lib/generator.rb, line 267
def object(name)
  strip_extension(name) + @ext[:o]
end
proj_build_directory(proj, cfg = configuration) click to toggle source
# File lib/generator.rb, line 358
def proj_build_directory(proj, cfg = configuration)
  # TODO: simplify
  proj_root = if project_used?(proj)
                used_project_options(proj)[:proj_root] || @proj_root
              else
                @proj_root
              end
  proj_root.join(proj, @build_prefix + configuration_suffix(cfg))
end
proj_existing_libraries(proj, cfg) click to toggle source
# File lib/generator.rb, line 326
def proj_existing_libraries(proj, cfg)
  proj_library_paths(proj, cfg).find_all { |p| File.exist?(p) }
end
proj_libraries(proj, cfg = configuration) click to toggle source
# File lib/generator.rb, line 577
def proj_libraries(proj, cfg = configuration)
  filename = proj_build_directory(proj, cfg).join(LIBRARIES_FILE_NAME)
  YAML.load_file(filename) || { libraries: [], library_paths: [] }
rescue StandardError
  raise "Could not read #{filename}. Did you generate #{proj} (#{cfg})?"
end
proj_library_base_name(proj) click to toggle source
# File lib/generator.rb, line 307
def proj_library_base_name(proj)
  proj.tr('/', '_')
end
proj_library_paths(proj, cfg = configuration) click to toggle source
# File lib/generator.rb, line 319
def proj_library_paths(proj, cfg = configuration)
  libso = proj_shared_object_file_name(proj)
  liba = proj_static_library_file_name(proj)
  path = proj_build_directory(proj, cfg)
  [path.join(libso), path.join(liba)].compact
end
proj_shared_object_file_name(proj) click to toggle source
# File lib/generator.rb, line 311
def proj_shared_object_file_name(proj)
  library_file_name(proj_library_base_name(proj), :so)
end
proj_static_library_file_name(proj) click to toggle source
# File lib/generator.rb, line 315
def proj_static_library_file_name(proj)
  library_file_name(proj_library_base_name(proj), :a)
end
read_directory() click to toggle source
# File lib/generator.rb, line 254
def read_directory
  @files = Dir.new(cwd).find_all { |f| !exclude?(f) && !File.directory?(f) }
end
read_init_files!() click to toggle source
# File lib/generator.rb, line 215
def read_init_files!
  files = [Pathname.new(File.dirname(__FILE__)).join('../tools')]
  files += config_files(:global)
  files += config_files(:user)
  files += [@proj_file] + @priv_files.reverse

  files = stable_unique_paths(files.compact).find_all do |p|
    if File.exist?(p)
      @@log.debug "did not find '#{p}'"
      true
    else
      false
    end
  end

  files.map(&:to_s).each do |f|
    if File.directory?(f)
      @@log.debug "load files in directory '#{f}'"
      Dir.glob(Pathname.new(f).join('*.rb')).each do |ff|
        @@log.debug "- load '#{ff}'"
        require(File.expand_path(ff))
      end
    else
      @@log.debug "load '#{f}'"
      if f =~ /\.rb^/
        require(f)
      else
        load(f)
      end
    end
  end

  files
end
store_persistent_data!() click to toggle source
# File lib/generator.rb, line 545
def store_persistent_data!
  store_used_packages
end
store_used_packages() click to toggle source
# File lib/generator.rb, line 559
def store_used_packages
  filename = create_this_build_directory.join(LIBRARIES_FILE_NAME)
  File.open(filename, 'w+b') do |f|
    if @@tools[LD]
      ld.unique!
      data = {
        used_packages: check_no_proc_value(used_packages),
        explicitly_used_packages: check_no_proc_value(explicitly_used_packages),
        projects: ld.projects,
        libraries: ld.libraries,
        library_paths: ld.library_paths
      }
      @@log.debug "write '#{filename}'"
      f.write(YAML.dump(data))
    end
  end
end
subninja() click to toggle source
# File lib/generator.rb, line 651
def subninja
  @subproject_paths.each do |p|
    ninja.subninja(Pathname.new(File.expand_path(p)).join(build_directory_name, ninja_file_name))
  end
end
subproject_name(path) click to toggle source
# File lib/generator.rb, line 621
def subproject_name(path)
  root = Pathname.new(@proj_root)
  sub = Pathname.new(File.expand_path(path))
  begin
    sub.relative_path_from(cwd) # check
    sub.relative_path_from(root).to_s
  rescue StandardError
    raise "'#{path}' is not a directory in '#{cwd}'"
  end
end
subproject_target(path) click to toggle source
# File lib/generator.rb, line 632
def subproject_target(path)
  "proj_#{proj_library_base_name(subproject_name(path))}"
end
subproject_targets() click to toggle source
# File lib/generator.rb, line 636
def subproject_targets
  @subproject_paths.map { |p| subproject_target(p) }
end
this_build_directory() click to toggle source
# File lib/generator.rb, line 334
def this_build_directory
  @cwd.join(build_directory_name)
end
this_shared_object_file_name() click to toggle source
# File lib/generator.rb, line 299
def this_shared_object_file_name
  library_file_name(@lib_base_name, :so)
end
this_static_library_file_name() click to toggle source
# File lib/generator.rb, line 303
def this_static_library_file_name
  library_file_name(@lib_base_name, :a)
end
tool_chain() click to toggle source

all tools (with dependencies) sorted by slot

# File lib/generator.rb, line 585
def tool_chain
  @@tools.values.sort_by(&:slot)
end
use_subprojects() click to toggle source
# File lib/generator.rb, line 640
def use_subprojects
  @subproject_paths.each do |p|
    proj = subproject_name(p)
    opts = @subproject_options[p] || {}
    opts[:subproject] = true
    opts[:project] = proj

    use_project(opts)
  end
end
wrap_target(target) click to toggle source
# File lib/generator.rb, line 418
def wrap_target(target)
  if intermediate_targets.include?(target) ||
     toplevel_targets.include?(target)
    "${project}_#{target}"
  else
    target
  end
end
wrap_targets_array(targets) click to toggle source
# File lib/generator.rb, line 436
def wrap_targets_array(targets)
  Array(targets).map { |w| wrap_target(w) }
end

Protected Instance Methods

check_no_proc_value(hash) click to toggle source

ensure there are lambdas (e.g., :lazy_updates) left in hash

# File lib/generator.rb, line 550
def check_no_proc_value(hash)
  hash.each do |k, v|
    raise "cannot store '#{k}=Proc'" if v.is_a?(Proc)
  end
  hash
end
provide_required_tools() click to toggle source
# File lib/generator.rb, line 519
def provide_required_tools
  if @@tools.empty?
    file = "$builddir/#{LIBRARIES_FILE_NAME}"
    @@log.warn "no tools specified: use 'touch #{file}'"
    touch.files << file
  else
    loop do
      required = {}
      @@tools.each do |name, instance|
        instance.requires.each do |klass|
          if !@@tools[klass] && !required[klass]
            required[klass] = klass.new
            @@log.debug "'#{name}' requires '#{klass}'"
          end
        end
      end

      break if required.empty?

      @@tools.merge!(required)
    end
  end
end
recursive_find_project_root(dir) click to toggle source
# File lib/generator.rb, line 147
def recursive_find_project_root(dir)
  if File.exist?(priv_path = dir.join(PRIV_FILE_NAME))
    @priv_files << priv_path
    @rmakefile_deps << priv_path
  end

  if dir.root?
    nil
  elsif File.exist?(path = dir.join(PROJ_FILE_NAME))
    @proj_file = path
    @rmakefile_deps << path
    dir.parent
  else
    recursive_find_project_root(dir.parent)
  end
end