class Rmk::Generator
Constants
- LIBRARIES_FILE_NAME
- TOPLEVEL_TARGETS
Attributes
Public Class Methods
Create ::new generator
(singleton) instance. Parameters in options:
:config-
#configuration, one of Rmk#configurations, use Rmk#default_configuration if not provided
:rmk_config-
rmkcommand used as generator (optional) :build_only-
partial initialization, generator is used only to spawn
ninjaprocess
# 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
# File lib/generator.rb, line 258 def add_file(name) @files << name name end
Add #object file.
-
name file name
-
main set if this file is not part of the project library.
main can have values
-
nilorfalsefor an #object that is part of the project library -
:mainfor a executable binary (i.e., an #object with a +main()+ function -
Hashwith fields:type,:file,:prefixand:suffixfor creating “standalone” shared #object (e.g., a Matlab MEX file). Rmk::LinkerLikeProcessor#collect_object_files and Rmk::LD#ninja_build take care of this.
# 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
# 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
# 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
# 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
# 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
# File lib/generator.rb, line 338 def build_directory(cfg) @cwd.join(build_directory_name(cfg)) end
# File lib/generator.rb, line 330 def build_directory_name(cfg = configuration) @build_prefix + configuration_suffix(cfg) end
# 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
# File lib/generator.rb, line 372 def configuration_suffix(cfg = configuration) if cfg.nil? || cfg == :default '' else '_' + cfg.to_s end end
# 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
# File lib/generator.rb, line 354 def create_proj_build_directory(proj, cfg = configuration) create_build_directory(proj_build_directory(proj, cfg)) end
# File lib/generator.rb, line 350 def create_this_build_directory create_build_directory(this_build_directory) end
# 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
# 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
# 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
# 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
# File lib/generator.rb, line 166 def define_system_file_extensions! @ext = { exe: '', o: '.o', so: '.so', a: '.a', lib_prefix: 'lib' } end
# 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
# File lib/generator.rb, line 250 def exclude?(name) !@exclude_patterns.find { |p| name =~ p }.nil? end
# File lib/generator.rb, line 657 def exclude_subproject?(path) !@exclude_subproject_patterns.find { |p| path =~ p }.nil? end
# File lib/generator.rb, line 263 def exe(name) strip_extension(name) + @ext[:exe] end
# File lib/generator.rb, line 368 def expand_source(src) "$srcdir/#{src}" end
# 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
# File lib/generator.rb, line 295 def library_file_name(base, type) @ext[:lib_prefix] + base + @ext[type] end
# 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
# 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
# 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
# File lib/generator.rb, line 477 def ninja_end @ninja.out.flush @ninja = nil @@log.debug "wrote ninja file '#{ninja_file_name}'" end
# File lib/generator.rb, line 380 def ninja_file_name(_cfg = configuration) # "build#{configuration_suffix(cfg)}.ninja" 'build.ninja' end
# 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
# 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
# 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
# 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
# File lib/generator.rb, line 267 def object(name) strip_extension(name) + @ext[:o] end
# 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
# File lib/generator.rb, line 326 def proj_existing_libraries(proj, cfg) proj_library_paths(proj, cfg).find_all { |p| File.exist?(p) } end
# 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
# File lib/generator.rb, line 307 def proj_library_base_name(proj) proj.tr('/', '_') end
# 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
# File lib/generator.rb, line 315 def proj_static_library_file_name(proj) library_file_name(proj_library_base_name(proj), :a) end
# File lib/generator.rb, line 254 def read_directory @files = Dir.new(cwd).find_all { |f| !exclude?(f) && !File.directory?(f) } end
# 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
# File lib/generator.rb, line 545 def store_persistent_data! store_used_packages end
# 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
# 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
# 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
# File lib/generator.rb, line 632 def subproject_target(path) "proj_#{proj_library_base_name(subproject_name(path))}" end
# File lib/generator.rb, line 636 def subproject_targets @subproject_paths.map { |p| subproject_target(p) } end
# File lib/generator.rb, line 334 def this_build_directory @cwd.join(build_directory_name) end
# File lib/generator.rb, line 303 def this_static_library_file_name library_file_name(@lib_base_name, :a) end
all tools (with dependencies) sorted by slot
# File lib/generator.rb, line 585 def tool_chain @@tools.values.sort_by(&:slot) end
# 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
# 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
# File lib/generator.rb, line 436 def wrap_targets_array(targets) Array(targets).map { |w| wrap_target(w) } end
Protected Instance Methods
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
# 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
# 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