summaryrefslogtreecommitdiff
path: root/import/omnibus.to_chunk
blob: 45ceb5a4cb20562e54a0ebbe95b2bfd5c045a311 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#!/usr/bin/env ruby
#
# Create a chunk morphology to integrate Omnibus software in Baserock
#
# Copyright (C) 2014  Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

require 'bundler'
require 'omnibus'

require 'optparse'
require 'rubygems/commands/build_command'
require 'rubygems/commands/install_command'
require 'shellwords'

require_relative 'importer_base'

# This DEFINITIONS#PROJECT thing is a bit shit. Make main.py smarter about
# being able to pass extra arguments to import extensions instead of forcing
# everything into two arguments.
BANNER = "Usage: omnibus.to_chunk DEFINITIONS_DIR#PROJECT_NAME SOFTWARE_NAME"

DESCRIPTION = <<-END
Generate a .morph file for a given Omnibus software component.
END

class Omnibus::Builder
  # It's possible to use `gem install` in build commands, which is a great
  # way of subverting the dependency tracking Omnibus provides. It's done
  # in `omnibus-chef/config/software/chefdk.rb`, for example.
  #
  # To handle this, here we extend the class that executes the build commands
  # to detect when `gem install` is run. It uses the Gem library to turn the
  # commandline back into a Bundler::Dependency object that we can use.
  #
  # We also trap `gem build` so we know when a software component is a RubyGem
  # that should be handled by 'rubygems.to_chunk'.

  class GemBuildCommandParser < Gem::Commands::BuildCommand
    def gemspec_path(args)
      handle_options args
      if options[:args].length != 1
        raise Exception, "Invalid `gem build` commandline: 1 argument " +
                         "expected, got #{options[:args]}."
      end
      options[:args][0]
    end
  end

  class GemInstallCommandParser < Gem::Commands::InstallCommand
    def dependency_list_from_commandline(args)
      handle_options args
      options[:args].collect do |gem_name|
        Bundler::Dependency.new(gem_name, options[:version])
      end
    end
  end

  def gem(command, options = {})
    # This function re-implements the 'gem' function in the build-commands DSL.
    if command.start_with? 'build'
      parser = GemBuildCommandParser.new
      args = Shellwords.split(command).drop(1)
      if built_gemspec != nil
        raise Exception, "More than one `gem build` command was run as part " +
                         "of the build process. The 'rubygems.to_chunk' " +
                         "program currently supports only one .gemspec " +
                         "build per chunk, so this can't be processed " +
                         "automatically."
      end
      @built_gemspec = parser.gemspec_path(args)
    elsif command.start_with? 'install'
      parser = GemInstallCommandParser.new
      args = Shellwords.split(command).drop(1)
      gems = parser.dependency_list_from_commandline(args)
      manually_installed_rubygems.concat gems
    end
  end

  def built_gemspec
    @built_gemspec
  end

  def manually_installed_rubygems
    @manually_installed_rubygems ||= []
  end
end

class OmnibusChunkMorphologyGenerator < Importer::Base
  def parse_options(arguments)
    opts = create_option_parser(BANNER, DESCRIPTION)

    parsed_arguments = opts.parse!(arguments)

    if parsed_arguments.length != 2 and parsed_arguments.length != 3
      STDERR.puts "Expected 2 or 3 arguments, got #{parsed_arguments}."
      opts.parse(['-?'])
      exit 255
    end

    project_dir_and_name, software_name, expected_version = parsed_arguments
    project_dir, _, project_name = project_dir_and_name.rpartition('#')
    # Not yet implemented
    #if expected_version != nil
    #  expected_version = Gem::Version.new(expected_version)
    #end
    [project_dir, project_name, software_name, expected_version]
  end

  ##these should be methods in Software
  #def software_needs_importing(software_name)
  #  software = Omnibus::Software.load(@project, software_name)
  #  if software.fetcher.instance_of?(Omnibus::PathFetcher)
  #    log.info(
  #      "Not adding #{software.name} as a dependency: it's installed from " +
  #      "a path which probably means that it is package configuration, not " +
  #      "a 3rd-party component to be imported.")
  #    false
  #  elsif software.fetcher.instance_of?(Omnibus::NullFetcher)
  #    gems = software.builder.manually_installed_rubygems
  #    if not gems.empty?
  #      log.info(
  #        "Adding #{software.name} as a dependency because it installs one " +
  #        "or more RubyGems manually: #{gems}")
  #      true
  #    end
  #    false
  #  else
  #    true
  #  end
  #end

  #def omnibus_deps_for_software(software)
  #  deps = software.dependencies.collect do |name|
  #    if software_needs_importing(name)
  #      [name, 0]
  #    else
  #      nil
  #    end
  #  end
  #  Hash[deps.compact]
  #end

  #def rubygems_deps_for_software(software)
  #  deps = software.builder.manually_installed_rubygems.collect do |dep|
  #    [dep.name, dep.requirement.to_s]
  #  end
  #  Hash[deps]
  #end

  def generate_chunk_morph_for_rubygems_software(software)
    scripts_dir = File.dirname(__FILE__)
    tool = File.join(scripts_dir, 'rubygems.to_chunk')
    command = [[tool, tool], 
    puts "Running #{tool} in #{scripts_dir}"
    io = IO.popen([[tool, tool], 'foo', 'bar', 'baz', 'kaz', :chdir => scripts_dir])
    text = io.read
    morphology = YAML::load(text)
    return morphology
  end

  def generate_chunk_morph_for_software(software)
    #omnibus_deps = omnibus_deps_for_software software
    #rubygems_deps = rubygems_deps_for_software software

    if software.builder.built_gemspec != nil
      morphology = generate_chunk_morph_for_rubygems_software(software)
    else
      morphology = {
        "name" => software.name,
        "kind" => "chunk",
      }
    end

    #gems = software.builder.manually_installed_rubygems
    #self_gems, dep_gems = gems.partition do |req|
    #  req.name
    #end

    ## FIXME: will overwrite the deps from rubygems
    #morphology.update({
    #  # Possibly this tool should look at software.build and
    #  # generate suitable configure, build and install-commands.
    #  # For now: don't bother!

    #  # FIXME: are these build or runtime dependencies? We'll assume both.
    #  "x-build-dependencies-omnibus" => omnibus_deps,
    #  "x-runtime-dependencies-omnibus" => omnibus_deps,

    #  "x-build-dependencies-rubygems" => {},
    #  "x-runtime-dependencies-rubygems" => rubygems_deps,
    #})
    if software.description
      morphology['description'] = software.description
    end
    morphology
  end

  def run
    project_dir, project_name, software_name = parse_options(ARGV)

    log.info("Creating chunk morph for #{software_name} from project " +
             "#{project_name}, defined in #{project_dir}")
    log.debug("Running in: #{Dir.getwd}")

    @project = Omnibus::Project.load(project_name)

    software = Omnibus::Software.load(@project, software_name)

    morph = generate_chunk_morph_for_software(software)

    write_morph(STDOUT, morph)
  end
end

OmnibusChunkMorphologyGenerator.new.run