class Ryb::Ninja::Generator

Public Class Methods

new(rybfile, opts) click to toggle source
# File lib/ryb/ninja.rb, line 4
def initialize(rybfile, opts)
  @rybfile = rybfile
  @root = opts.fetch(:root, '.')
  @build = opts.fetch(:build, '_build')
  @ninjafile = ::Ninja::File.new
  @path = opts.fetch(:ninjafile, "#{@root}/build.ninja")

  # HACK(mtwilliams): Create directory structure.
  require 'fileutils'
  FileUtils.mkdir_p(["#@build/bin", "#@build/lib", "#@build/obj"])

  # HACK(mtwilliams): Assume VisualStudio.
  @vs = ::VisualStudio.latest
  @vc = @vs.products[:c_and_cpp]
end

Public Instance Methods

generate() click to toggle source
# File lib/ryb/ninja.rb, line 20
def generate
  @ninjafile.rule "mv", "move /Y $in $out"
  @ninjafile.rule "cp", "move /Y /B $in $out"

  @ninjafile.save(@path)
end
on_product(project, product) click to toggle source
# File lib/ryb/ninja.rb, line 32
def on_product(project, product)
  puts " -> Generating for #{product.name.pretty || product.name}"
end
on_product_triplet(project, product, tripletised) click to toggle source
# File lib/ryb/ninja.rb, line 39
def on_product_triplet(project, product, tripletised)
  tripletised = tripletised.()

  config = tripletised.configuration.name.to_sym
  platform = tripletised.platform.name.to_sym
  arch = tripletised.architecture.name.to_sym

  puts "  ~> #{config} for #{platform} (#{arch})"

  name = product.name.canonicalize
  namespace = "#{name}_#{tripletised.triplet.join('_')}"

  sdk = @vc.sdks[:windows].select{|sdk| sdk.version.to_f >= 7.1}.first
  sys_include_paths = @vc.includes + sdk.includes
  sys_lib_paths = @vc.libraries[arch] + sdk.libraries[arch]

  @ninjafile.variable "#{namespace}_cflags_sys", VisualStudio::Compiler.include_paths_to_flags(sys_include_paths).join(' ')
  @ninjafile.variable "#{namespace}_cxxflags_sys", "$#{namespace}_cflags_sys"
  @ninjafile.variable "#{namespace}_ldflags_sys", VisualStudio::Linker.library_paths_to_flags(sys_lib_paths).join(' ')

  cflags = cflags_for_product(project, product, tripletised)
  arflags = arflags_for_product(project, product, tripletised)
  ldflags = ldflags_for_product(project, product, tripletised)

  @ninjafile.variable "#{namespace}_cflags", cflags.join(' ')
  @ninjafile.variable "#{namespace}_cxxflags", "$#{namespace}_cflags"
  @ninjafile.variable "#{namespace}_arflags", arflags.join(' ')
  @ninjafile.variable "#{namespace}_ldflags", ldflags.join(' ')

  sources = sources_for_product(product, tripletised)
  @ninjafile.variable "#{namespace}_sources", sources.map{|src| "#@root/#{src.path}"}.join(' ')

  deps = deps_for_product(product, tripletised)
  @ninjafile.variable "#{namespace}_deps", deps.join(' ')

  artifacts = artifacts_for_product(product, tripletised)
  @ninjafile.variable "#{namespace}_artifacts", artifacts.join(' ')
  @ninjafile.variable("#{namespace}_as_linkable", artifacts[0]) if product.is_a? Ryb::Library
  @ninjafile.variable "#{namespace}_symbols", artifacts[0].gsub(/\.(exe|lib|dll)/, ".pdb")

  # https://github.com/ninja-build/ninja/blob/d1763746b65cc7349d4ed9478befdb651aa24589/src/msvc_helper_main-win32.cc#L38
  env_block = "#@build/#{namespace}.env_block"
  env = @vc.environment(target: {platform: :windows, architecture: arch}, base: environment)
  # "PATH=#{(@vc.binaries[arch] + sdk.binaries[arch]).join(';')}\0"
  File.write(env_block, env.map{|env_var, value| "#{env_var}=#{value}"}.join("\0"))

  @ninjafile.rule(
    "cc_#{namespace}",
    "ninja -t msvc -e #{env_block} -- cl.exe /showIncludes $#{namespace}_cflags_sys $#{namespace}_cflags /Fd$#{namespace}_symbols /Fo$out /Tc$in",
    dependencies: :msvc
  )

  @ninjafile.rule(
    "cxx_#{namespace}",
    "ninja -t msvc -e #{env_block} -- cl.exe /showIncludes $#{namespace}_cxxflags_sys $#{namespace}_cxxflags /Fd$#{namespace}_symbols /Fo$out /Tp$in",
    dependencies: :msvc
  )

  @ninjafile.rule(
    "ar_#{namespace}",
    "ninja -t msvc -e #{env_block} -- lib.exe @$out.rsp",
    response_file: "$#{namespace}_arflags /OUT:$out $in"
  )

  @ninjafile.rule(
    "ld_#{namespace}",
    "ninja -t msvc -e #{env_block} -- link.exe @$out.rsp",
    response_file: "$#{namespace}_ldflags_sys $#{namespace}_ldflags /OUT:$out $#{namespace}_deps $in"
  )

  @ninjafile.rule(
    "so_#{namespace}",
    "ninja -t msvc -e #{env_block} -- link.exe @$out.rsp",
    response_file: "$#{namespace}_ldflags_sys $#{namespace}_ldflags /DLL /IMPLIB:$#{namespace}_as_linkable /OUT:$out $#{namespace}_deps $in"
  )

  c_sources = sources.select{|src| src.language == :c}.map(&:path).map{|src| "#@root/#{src}"}
  c_objects = c_sources.map{|src| src.gsub(/\.(c)/, ".#{namespace}.obj")}.map{|obj| "#@build/obj/#{obj}"}
  cxx_sources = sources.select{|src| src.language == :cpp}.map(&:path).map{|src| "#@root/#{src}"}
  cxx_objects = cxx_sources.map{|src| src.gsub(/\.(cc|cpp|cxx|c++)/, ".#{namespace}.obj")}.map{|obj| "#@build/obj/#{obj}"}

  @ninjafile.build "cc_#{namespace}", Hash[c_objects.zip(c_sources)]
  @ninjafile.build "cxx_#{namespace}", Hash[cxx_objects.zip(cxx_sources)]

  case product
    when Ryb::Application
      @ninjafile.build "ld_#{namespace}", {artifacts[0] => c_objects + cxx_objects}
      @ninjafile.alias namespace, artifacts[0]
    when Ryb::Library
      case product.linkage
        when :static
          @ninjafile.build "ar_#{namespace}", {artifacts[0] => c_objects + cxx_objects}
          @ninjafile.alias namespace, artifacts[0]
        when :dynamic
          @ninjafile.build "so_#{namespace}", {artifacts[1] => c_objects + cxx_objects}
          @ninjafile.alias namespace, artifacts[1]
        end
    end
end
on_project(project) click to toggle source

TODO(mtwilliams): Generate phony commands that proxy to all triplets.

# File lib/ryb/ninja.rb, line 28
def on_project(project)
  puts "=> #{project.name.pretty || project.name}"
end
on_project_triplet(project, tripletised) click to toggle source
# File lib/ryb/ninja.rb, line 36
def on_project_triplet(project, tripletised)
end

Private Instance Methods

arflags_for_product(_project, _product, _tripletised) click to toggle source
# File lib/ryb/ninja.rb, line 241
def arflags_for_product(_project, _product, _tripletised)
  %w{/nologo}
end
artifacts_for_product(product, tripletised) click to toggle source
# File lib/ryb/ninja.rb, line 208
def artifacts_for_product(product, tripletised)
  name = product.name.canonicalize
  config_suffix = tripletised.configuration.suffix || "_#{tripletised.configuration.name}"
  platform_suffix = tripletised.platform.suffix || "_#{tripletised.platform.name}"
  arch_suffix = tripletised.architecture.suffix || "_#{tripletised.architecture.name}"
  suffix = [config_suffix, platform_suffix, arch_suffix].join('')

  if product.is_a? Ryb::Application
    ["#@build/bin/#{name}#{suffix}.exe"]
  elsif product.is_a? Ryb::Library
    case product.linkage
      when :static
        ["#@build/lib/#{name}#{suffix}.lib"]
      when :dynamic
        ["#@build/lib/#{name}#{suffix}.lib", "#@build/bin/#{name}#{suffix}.dll"]
      end
  end
end
cflags_for_architecture(arch) click to toggle source
# File lib/ryb/ninja.rb, line 277
def cflags_for_architecture(arch)
  flags = []
  # TODO(mtwilliams): @toolchains.for(target)
  flags += VisualStudio::Compiler.architecture_to_flags(arch.name.canonicalize.to_sym)
  flags += VisualStudio::Compiler.include_paths_to_flags(arch.paths.includes) if arch.paths
  flags += VisualStudio::Compiler.defines_to_flags(arch.defines)
  flags
end
cflags_for_configuration(config) click to toggle source
# File lib/ryb/ninja.rb, line 258
def cflags_for_configuration(config)
  flags = []
  # TODO(mtwilliams): @toolchains.for(target)
  flags += VisualStudio::Compiler.include_paths_to_flags(config.paths.includes) if config.paths
  flags += VisualStudio::Compiler.defines_to_flags(config.defines)
  flags += VisualStudio::Compiler.treat_warnings_as_errors_to_flag(config.treat_warnings_as_errors)
  flags += VisualStudio::Compiler.generate_debug_symbols_to_flag(config.generate_debug_symbols)
  flags += VisualStudio::Compiler.optimization_to_flags(config.optimize)
  flags
end
cflags_for_platform(platform) click to toggle source
# File lib/ryb/ninja.rb, line 269
def cflags_for_platform(platform)
  flags = []
  # TODO(mtwilliams): @toolchains.for(target)
  flags += VisualStudio::Compiler.include_paths_to_flags(platform.paths.includes) if platform.paths
  flags += VisualStudio::Compiler.defines_to_flags(platform.defines)
  flags
end
cflags_for_product(project, product, tripletised) click to toggle source
# File lib/ryb/ninja.rb, line 228
def cflags_for_product(project, product, tripletised)
  flags = VisualStudio::Compiler::STANDARD_FLAGS +
          cflags_for_configuration(tripletised.configuration) +
          cflags_for_platform(tripletised.platform) +
          cflags_for_architecture(tripletised.architecture)
  flags += VisualStudio::Compiler.include_paths_to_flags(project.paths.includes) if project.paths
  flags += VisualStudio::Compiler.include_paths_to_flags(product.paths.includes) if product.paths
  flags += VisualStudio::Compiler.defines_to_flags(project.defines)
  flags += VisualStudio::Compiler.defines_to_flags(product.defines)
  flags += VisualStudio::Compiler.architecture_to_flags(tripletised.architecture.name.to_sym)
  flags
end
deps_for_product(product, tripletised) click to toggle source
# File lib/ryb/ninja.rb, line 191
def deps_for_product(product, tripletised)
  deps = [*product.dependencies] |
         [*tripletised.configuration.dependencies] |
         [*tripletised.platform.dependencies] |
         [*tripletised.architecture.dependencies]
  deps.map do |dep|
    case dep
      when Ryb::InternalDependency
        "${#{dep.product}_#{tripletised.triplet.join('_')}_as_linkable}"
      when Ryb::ExternalDependency
        triplet = [tripletised.configuration, tripletised.platform, tripletised.architecture].map(&:name).map(&:to_sym)
        mangled = dep.mangled(*triplet)
        "#{mangled}.lib"
      end
  end
end
environment() click to toggle source
# File lib/ryb/ninja.rb, line 140
def environment
  variables = [
    'ALLUSERSPROFILE',
    'APPDATA',
    'CommonProgramFiles',
    'CommonProgramFiles(x86)',
    'CommonProgramW6432',
    'ComSpec',
    'HOMEDRIVE',
    'HOMEPATH',
    'LOCALAPPDATA',
    'Path',
    'PATHEXT',
    'ProgramData',
    'ProgramFiles',
    'ProgramFiles(x86)',
    'ProgramW6432',
    'PUBLIC',
    'SystemDrive',
    'SystemRoot',
    'TEMP',
    'TMP',
    'USERDOMAIN',
    'USERNAME',
    'USERPROFILE',
    'windir'
  ]

  @environment ||= begin
    require 'win32/registry'
    ::Win32::Registry::HKEY_LOCAL_MACHINE.open("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", ::Win32::Registry::KEY_READ) do |originals|
      variables.map { |variable|
        value = begin
          originals[variable]
        rescue
          ENV[variable]
        end
        [variable, value]
      }.to_h
    end
  end
end
ldflags_for_architecture(arch) click to toggle source
# File lib/ryb/ninja.rb, line 301
def ldflags_for_architecture(arch)
  flags = []
  # TODO(mtwilliams): @toolchains.for(target)
  flags += VisualStudio::Linker.library_paths_to_flags(arch.paths.libraries) if arch.paths
  flags
end
ldflags_for_configuration(config) click to toggle source
# File lib/ryb/ninja.rb, line 286
def ldflags_for_configuration(config)
  flags = []
  # TODO(mtwilliams): @toolchains.for(target)
  flags += VisualStudio::Linker.library_paths_to_flags(config.paths.libraries) if config.paths
  flags += VisualStudio::Linker.generate_debug_symbols_to_flag(config.generate_debug_symbols)
  flags
end
ldflags_for_platform(platform) click to toggle source
# File lib/ryb/ninja.rb, line 294
def ldflags_for_platform(platform)
  flags = []
  # TODO(mtwilliams): @toolchains.for(target)
  flags += VisualStudio::Linker.library_paths_to_flags(platform.paths.libraries) if platform.paths
  flags
end
ldflags_for_product(project, product, tripletised) click to toggle source
# File lib/ryb/ninja.rb, line 245
def ldflags_for_product(project, product, tripletised)
  flags = VisualStudio::Linker::STANDARD_FLAGS +
          ldflags_for_configuration(tripletised.configuration) +
          ldflags_for_platform(tripletised.platform) +
          ldflags_for_architecture(tripletised.architecture)
  flags += VisualStudio::Linker.library_paths_to_flags(project.paths.libraries) if project.paths
  flags += VisualStudio::Linker.library_paths_to_flags(product.paths.libraries) if product.paths
  flags += VisualStudio::Linker.architecture_to_flags(tripletised.architecture.name.to_sym)
  # TODO: Linkage.
  flags
end
sources_for_product(product, tripletised) click to toggle source
# File lib/ryb/ninja.rb, line 183
def sources_for_product(product, tripletised)
  sources = [*product.sources] |
            [*tripletised.configuration.sources] |
            [*tripletised.platform.sources] |
            [*tripletised.architecture.sources]
  sources.reject{|src| src.inconsequential}.compact.uniq
end