mtwilliams/ninja

View on GitHub
lib/ninja/file.rb

Summary

Maintainability
B
5 hrs
Test Coverage
module Ninja
  class File
    def initialize(path=nil, &block)
      @variables = []
      @rules = []
      @builds = []
      @defaults = []
      Delegator.new(self, :except => [:save]).instance_eval(&block) if block_given?
      self.save(path) if path
    end

    def variable(name, value)
      @variables.push(Ninja::Variable.new(name, value))
    end

    def rule(name, command, opts={})
      additional = {}

      if opts[:response_file]
        additional[:response_file] = Ninja::ResponseFile.new("$out.rsp", opts[:response_file])
      end

      @rules.push(Ninja::Rule.new(:name => name,
                                  :command => command,
                                  :dependencies => opts[:dependencies],
                                  **additional))
    end

    def build(rule, outputs_to_inputs={})
      outputs_to_inputs.each do |output, inputs|
        @builds.push(Ninja::Build.new(:rule => rule, :inputs => [*inputs], :output => output))
      end
    end

    def alias(from, to)
      # Pretty clever, huh?
      @builds.push(Ninja::Build.new(:rule => 'phony', :inputs => [*to], :output => from))
    end

    def defaults(outputs)
      # TODO(mtwilliams): Accept variables (\$[\w]|\$\{[\w]\}).
      # raise "Expected output(s) to be paths." unless [*outputs].all?{|output| /\A(?:[-\w\.]+\/?)+\z/.match(output)}
      @defaults.push(*outputs)
    end

    def save(path)
      raise "Path not specified!" unless path
       # TODO(mtwilliams): Check if everything up to |path| exists.
      ::File.open(path, 'w') do |f|
        f.write "# This file was auto-generated by \"#{::File.basename($PROGRAM_NAME, ::File.extname($0))}\".\n"
        f.write "# Do not modify! Instead, modify the aforementioned program.\n\n"
        f.write "# We require Ninja >= 1.3 for `deps` and >= 1.5 for `msvc_deps_prefix`.\n"
        f.write "ninja_required_version = 1.5\n\n"

        @variables.each do |variable|
          # TODO(mtwilliams): Escape.
          f.write "#{variable.name} = #{variable.value}\n"
        end
        f.write "\n" unless @variables.empty?

        @rules.each do |rule|
          f.write "rule #{rule.name}\n"
          if rule.dependencies
            if (rule.dependencies == :gcc) or (rule.dependencies == :clang)
              f.write "  depfile = $out.d\n"
              f.write "  deps = gcc\n"
            elsif rule.dependencies == :msvc
              # TODO(mtwilliams): Handle non-English output.
              # f.write "  msvc_deps_prefix = Note: including file: \n"
              f.write "  deps = msvc\n"
            else
              f.write "  depfile = #{rule.dependencies}\n"
            end
          end
          f.write "  command = #{rule.command}\n"
          if rule.response_file
            f.write "  rspfile = #{rule.response_file.name}\n"
            f.write "  rspfile_content = #{rule.response_file.contents}\n"
          end
          f.write "\n"
        end

        @builds.each do |build|
          f.write "build #{build.output}: #{build.rule} #{build.inputs.join(' ')}\n"
        end

        unless @defaults.empty?
          f.write "\n" unless @builds.empty?
          f.write "default #{@defaults.join(' ')}\n" unless @defaults.empty?
        end

        # TODO(mtwilliams): Execute other files (via 'subninja').
        # TODO(mtwilliams): Specify pools, to optimize compilation times.
      end
    end
  end
end