Class Sprout::Sprout
In: sprout/lib/sprout.rb
Parent: Object

Sprouts is an open-source, cross-platform project generation and configuration tool for ActionScript 2, ActionScript 3, Adobe AIR and Flex projects. It is built on top of Ruby Gems, Rubigen Generators and is intended to work on any platform that Ruby runs on including specifically, Windows XP, Windows Vista, Cygwin, OS X and Linux.

Sprouts can be separated into some core concepts as follows:


Tools

A Tool is a Ruby Gem that usually refers to an executable or binary application. These applications are either natively cross platform, or the Ruby Gem should include a YAML document that tells Sprouts where to go in order to get the appropriate binary for which platform the user is currently running.

CLI Tools are usually referenced by subclasses of Sprout::ToolTask.

Once installed, many Tool Sprouts are made available from your path. For example if you install the sprout-mtasc-tool gem, from that point forward you can execute mtasc from the terminal as follows:

  mtasc -help # Should throw an error
  sudo gem install sprout-mtasc-tool
  mtasc -help # Should download and execute mtasc

Using just Sprout tools by themselves, we now have have the ability to install and manage requisite executables across platforms with zero configuration.

In reality, ‘Tool Sprouts’ are actually nothing more than a naming convention and expected gem configuration, but once those requirements are met, the core Sprout::Sprout can do some important work with them.


Libraries

A Library is simply shared code. Some libraries are distributed with only source, others are only pre-compiled binaries (SWC for ActionScript libraries), and still others are made available in both forms.

The Sprout::LibraryTask will download and copy a remote library sprout gem. The remote archive can include (or reference) either source or a pre-compiled file. For ActionScript libraries, this would be a SWC file.

This task is integrated with some of the compiler tasks in such a way that if an Sprout::MXMLCTask has any number of library tasks in it‘s prerequisites list, each of those libraries will be added to the compiler directive appropriately.

Following is a simple example of a library task. Using only this simple task definition, the Adobe corelib library sprout gem will be downloaded, installed and copied to your Sprout::ProjectModel lib_dir.

  library :corelib

By adding this named task as a prerequisite to your compilation task, that SWC will also be added to the Sprout::MXMLCTask library_path parameter.

  mxmlc 'bin/SomeProject.swf' => :corelib

You can also specify a particular library gem version if the library has changed since your project began.

  library :asunit3 do |t|
    t.version = '3.0.1'
  end

This will ensure that only that particular library version is used for this project.

You may want to refer to a library using a particular task name, but have it use a different library sprout gem. This can be done using the gem_name parameter as follows:

  library :asunit do |t|
    t.gem_name = 'sprout-asunit3-library'
  end

This may be useful because now the AsUnit sources will be installed to:

  lib/asunit

instead of:

  lib/asunit3

and you can now depend on this library as simply +:asunit+ in your compiler tasks.

You can easily create your own library gems using the Sprout::GemWrapTask and then refer to them by gem name.

In order to share your library tasks, you will need to do one of the following:

  • Tell interested developers to manually install your library gem
  • Upload your gem to any Rubyforge project file releases area.
      If your gem name begins with 'sprout-' and ends with '-library', you (and others) can refer to it by only
      the string in between that prefix and suffix. Otherwise, you (and others) will always have
      to set the gem_name parameter to the full name of your custom library.
    
  • Submit your library for inclusion from the ProjectSprouts project.
  • Add your gem to your own custom gem_server, and set up your rakefiles to pull from that server

You can search for all available libraries as follows:

  gem search -r sprout-*library

Only results that begin with ‘sprout-’ are known, valid libraries.


Bundles

A Sprout Bundle is a collection of Ruby code that supports a particular interest or technology. At the time of this writing, we have two bundles available.

  • ActionScript 2 Bundle (link) which supports ActionScript 2.0 development
  • ActionScript 3 Bundle (link) which supports ActionScript 3.0, MXML and AIR development

Bundles are the named entry point that the sprout shell tool uses to find project generators.

Bundles should be packaged and published to the RubyForge gem repository with very specific names as follows:

sprout-#{bundle_name}-bundle where ${bundle_name} is what will be given to the -n parameter of the sprout gem.

The as3 bundle is released as sprout-as3-bundle on RubyForge, but we can simply enter the short name when creating new as3 projects.


Generators

A SproutGenerator is a set of specifically configured folders and files that have been placed in a particular, expected location on disk. The Sprout generator feature is a minor modification to the standard Rubigen generators.

Sprouts modifies the underlying Rubigen Generator implementation in that we need support for multiple languages or technologies while Rubigen is able to simply expect that it‘s generating Ruby code.

Generators can exist in multiple different locations on disk, to learn how to create a new generator, see the Rubigen documentation.

To use a new or existing generator, simply enter it‘s name from within a project after calling

  script/generate

When a string is passed to the generate command, sprouts will look in the following locations in the following order with ‘name’ being the generator name that you have requested:

  • #{project_path}/generators/#{name}
  • #{project_path}/script/generators/#{name}
  • #{project_path}/vendor/generators/#{name}
  • #{Sprout::Sprout.sprout_cache}/generators/#{Sprout::ProjectModel.language}/#{name}
  • All Rubygems with a name ending with ’-bundle’ and with contents that match ’/lib/sprout/**/generators/[name]’

This means that when you have a new project and enter:

  script/generate foo

We will first look in your project for, ‘generators/foo’, ‘script/generators/foo’ and ‘vendor/generators/foo’.

Assuming no viable generator is found in your project, we will then look in your Sprout::Sprout sprout_cache for a folder named ‘generators/foo’.

Assuming no viable generator is found in your system wide path, we will begin looking inside of installed Ruby Gems. The expected gem will have a file at:

  lib/sprout/**/generators/foo/foo_generator.rb

If no named generator is found in any of these places an exception will be encountered.

Sprouts generators can be initiated from one of two places, either from a project directory with script/generate or directly from the Sprout gem.

When executing generators directly from the Sprout gem, you must send in a bundle base name and know that only ‘project’ generators found in that bundle will be executed.

When executing generators from a project, the Sprout::ProjectModel language parameter is used to determine the bundle (if necessary), and then the Generator name is used to execute any found generator.


Tasks

In Sprouts, a Task is referring to a Rake Task.

Rake is the automated build to written in Ruby. This tool is similar to Ant and Make if you‘re familiar with those technologies.

The main thing that differentiates Rake from it‘s competitors is the fact that Rake tasks are defined and configured in Ruby code rather than XML or C. This lets us more easily avoid repetition throughout a rakefile, and we gain the full power of the Ruby language to apply to our build scripts.

Essentially, Rake allows us to write and maintain much smaller, more digestible build scripts.

To learn more about Rake, check out Martin Fowler‘s seminal article on the subject.

At the time of this writing, Sprouts makes the following Rake tasks available:


Sprout

Tools, Libraries and Bundles are distributed as RubyGems and given a specific gem name suffix. For some examples:

  sprout-flex3sdk-tool
  sprout-asunit-library
  sprout-as3-bundle

The Sprout application provides shared functionality for each of the different types of Sprouts.

The Sprout command line tool primarily provides access to project generators from any sprout bundle that is available to your system, either locally or from the network.

When executed from the system path, this class will download and install a named bundle, and execute a project generator within that bundle. Following is an example:

  sprout -n as3 SomeProject

The previous command will download and install the latest version of the sprout-as3-bundle gem and initiate the project_generator with a single argument of ‘SomeProject’. If the string passed to the -n parameter begins with ‘sprout-’ it will be unmodified for the lookup. For example:

  spout -n sprout-as3-bundle SomeProject

will not have duplicate strings prepended or suffixed.


Some additional resources or references:

Rake: rake.rubyforge.org martinfowler.com/articles/rake.html

RubyGems:

Ruby Raven (Mostly Inspiration)

Methods

Public Class methods

Look in the provided dir for files that meet the criteria to be a valid Rakefile.

[Source]

# File sprout/lib/sprout.rb, line 457
    def self.child_rakefile(dir)
      @@default_rakefiles.each do |file|
        rake_path = File.join(dir, file)
        if(File.exists?(rake_path))
          return rake_path
        end
      end
      return nil
    end

Retrieve the RubyGems gem spec for a particular gem name that meets the provided requirements. requirements are provided as a string value like:

  '>= 0.0.1'

or

  '0.0.1'

This method will actually download and install the provided gem by name and requirements if it is not found locally on the system.

[Source]

# File sprout/lib/sprout.rb, line 355
    def self.find_gem_spec(name, requirements=nil, recursed=false)
      specs = Gem::cache.sprout_search(/.*#{name}$/).reverse # Found specs are returned in order from oldest to newest!?
      requirement = nil
      if(requirements)
        requirement = Gem::Requirement.new(requirements)
      end
      specs.each do |spec|
        if(requirements)
          if(requirement.satisfied_by?(spec.version))
            return spec
          end
        else
          return spec
        end
      end

      if(recursed)
        raise SproutError.new("Gem Spec not found for #{name} #{requirements}")
      else
        msg = ">> Loading gem [#{name}]"
        msg << " #{requirements}" if requirements
        msg << " from #{gem_sources.join(', ')} with its dependencies"
        Log.puts msg
        parts = [ 'ins', '-r', name ]
        # This url should be removed once released, released gems should be hosted from the rubyforge
        # project, and development gems will be hosted on our domain.
        parts << "--source #{gem_sources.join(' --source ')}" if(Log.debug || name.index('sprout-'))
        parts << "-v #{requirements}" unless requirements.nil?

        self.load_gem(parts.join(" "))
        Gem::cache.refresh!
        return find_gem_spec(name, requirements, true)
      end
    end

Return the sprout_cache combined with the passed in name and version so that you will get a cache location for a specific gem.

[Source]

# File sprout/lib/sprout.rb, line 344
    def self.gem_file_cache(name, version)
      return File.join(sprout_cache, "#{name}-#{version}")
    end

TODO: This command should accept an array of sprout names to fall back on… for example: generate([‘flex4’, ‘as3’], …)

[Source]

# File sprout/lib/sprout.rb, line 136
    def self.generate(sprout_name, generator_name, params, project_path=nil)
      # params.each_index do |index|
      #   params[index] = clean_project_name(params[index])
      # end
      RubiGen::Base.use_sprout_sources!(sprout_name, project_path)
      generator = RubiGen::Base.instance(generator_name, params)
      generator.command(:create).invoke!
    end

Retrieve the file target to an executable by sprout name. Usually, these are tool sprouts.

  • name Full sprout gem name that contains an executable file
  • archive_path Optional parameter for tools that contain more than one executable, or for

when you don‘t want to use the default executable presented by the tool. For example, the Flex SDK has many executables, when this method is called for them, one might use something like:

  Sprout::Sprout.get_executable('sprout-flex3sdk-tool', 'bin/mxmlc')
  • version Optional parameter to specify a particular gem version for this executable

[Source]

# File sprout/lib/sprout.rb, line 252
    def self.get_executable(name, archive_path=nil, version=nil)
      target = self.sprout(name, version)
      if(archive_path)
        # If caller sent in a relative path to an executable (e.g., bin/mxmlc), use it
        exe = File.join(target.installed_path, archive_path)
        if(User.new.is_a?(WinUser) && !archive_path.match(/.exe$/))
          # If we're on Win (even Cygwin), add .exe to support custom binaries (see sprout-flex3sdk-tool)
          if(File.exists?(exe + '.exe'))
            exe << '.exe'
          end
        end
      elsif(target.url)
        # Otherwise, use the default path to an executable if the RemoteFileTarget has a url prop
        exe = File.join(target.installed_path, target.archive_path)
      else
        # Otherwise attempt to run the feature from the system path
        exe = target.archive_path
      end
      
      if(!File.exists?(exe))
        raise UsageError.new("Could not retrieve requested executable from path: #{exe}")
      end
      
      if(File.exists?(exe) && !File.directory?(exe) && File.stat(exe).executable?)
        File.chmod 0755, exe
      end
      
      return exe
    end

Retrieve the full path to an executable that is available in the system path

[Source]

# File sprout/lib/sprout.rb, line 208
    def self.get_executable_from_path(exe)
      path = ENV['PATH']
      file_path = nil
      path.split(get_path_delimiter).each do |p|
        file_path = File.join(p, exe)
#        file_path = file_path.split("/").join("\\")
#        file_path = file_path.split("\\").join("/")
        if(File.exists?(file_path))
          return User.clean_path(file_path)
        end
      end
      return nil
    end

Build up the platform-specific preamble required to call the gem binary from Kernel.execute

[Source]

# File sprout/lib/sprout.rb, line 193
    def self.get_gem_preamble
      usr = User.new()
      if(!usr.is_a?(WinUser))
        # Everyone but Win and Cygwin users get 'sudo '
        return "#{SUDO_INSTALL_GEMS ? 'sudo ' : ''}gem"
      elsif(!usr.is_a?(CygwinUser))
        # We're in the DOS Shell
        return "ruby #{get_executable_from_path('gem')}"
      end
      # We're either a CygwinUser or some other non-sudo supporter
      return 'gem'
    end

[Source]

# File sprout/lib/sprout.rb, line 467
    def self.get_implicit_project_path(path)
      # We have recursed to the root of the filesystem, return nil
      if(path.nil? || path == '/' || path.match(/[A-Z]\:\//))
        return Dir.pwd
      end
      # Look for a rakefile as a child of the current path
      if(child_rakefile(path))
        return path
      end
      # No rakefile and no root found, check in parent dir
      return Sprout.get_implicit_project_path(File.dirname(path))
    end

[Source]

# File sprout/lib/sprout.rb, line 222
    def self.get_path_delimiter
      usr = User.new
      if(usr.is_a?(WinUser) && !usr.is_a?(CygwinUser))
        return ';'
      else
        return ':'
      end
    end

Return the home directory for this Sprout installation

[Source]

# File sprout/lib/sprout.rb, line 319
    def self.home
      return @@home
    end

Do not copy files found in the ignore_files list

[Source]

# File sprout/lib/sprout.rb, line 401
    def self.ignore_file? file
      @@COPY_IGNORE_FILES.each do |name|
        if(name == file)
          return true
        end
      end
      return false
    end

[Source]

# File sprout/lib/sprout.rb, line 337
    def self.inferred_sprout_cache
      home = User.application_home(@@name)
      return File.join(home, @@cache, "#{VERSION::MAJOR}.#{VERSION::MINOR}")
    end

[Source]

# File sprout/lib/sprout.rb, line 390
    def self.load_gem(args)
      # This must use a 'system' call because RubyGems
      # sends an 'exit'?
      system("#{get_gem_preamble} #{args}")
    end

Return the current project_name assuming someone has already set it, otherwise return an empty string

[Source]

# File sprout/lib/sprout.rb, line 431
    def self.project_name
      @@project_name ||= ''
    end

project_path should step backward in the file system until it encounters a rakefile. The parent directory of that rakefile should be returned. If no rakefile is found, it should return Dir.pwd

[Source]

# File sprout/lib/sprout.rb, line 444
    def self.project_path
      @@project_path ||= self.project_path = get_implicit_project_path(Dir.pwd)
    end

Return the rakefile in the current project_path

[Source]

# File sprout/lib/sprout.rb, line 449
    def self.project_rakefile
      if(!defined?(@@project_rakefile))
        path = project_path
      end
      return @@project_rakefile ||= nil
    end

Remove all installed RubyGems that begin with the string ‘sprout’ and clear the local sprout cache

[Source]

# File sprout/lib/sprout.rb, line 146
    def self.remove_all
      # Set up sudo prefix if not on win machine
      # Only show confirmation if there is at least one installed sprout gem
      confirmation = false
      count = 0
      # For each sprout found, remove it!
      RubiGen::GemGeneratorSource.new().each_sprout do |sprout|
        count += 1
        command = "#{get_gem_preamble} uninstall -x -a -q #{sprout.name}"

        if(!confirmation)
          break unless confirmation = remove_gems_confirmation
        end
        puts "executing #{command}"
        raise ">> Exited with errors: #{$?}" unless system(command)
      end
      
      if(confirmation)
        puts "All Sprout gems have been successfully uninstalled"
      elsif(count > 0)
        puts "Some Sprout gems have been left on the system"
      else
        puts "No Sprout gems were found on this system"
      end

      # Now clear out the cache
      cache = File.dirname(File.dirname(Sprout.sprout_cache))
      
      if(File.exists?(cache))
        puts "\n[WARNING]\n\nAbout to irrevocably destroy the sprout cache at:\n\n#{cache}\n\n"
        puts "Are you absolutely sure? [Yn]"
        response = $stdin.gets.chomp!
        if(response.downcase.index('y'))
          FileUtils.rm_rf(cache)
        else
          puts "Leaving the Sprout file cache in tact...."
        end
      else
        puts "No cached files found on this system"
      end
      
      puts "To completely remove sprouts now, run:"
      puts "  #{get_gem_preamble} uninstall sprout"
    end

[Source]

# File sprout/lib/sprout.rb, line 231
    def self.remove_gems_confirmation
      msg ="About to uninstall all RubyGems that match 'sprout-'....\nAre you sure you want to do this? [Yn]\n" 
      puts msg
      response = $stdin.gets.chomp!
      if(response.downcase.index('y'))
        return true
      end
      return false
    end

Allows us to easily download and install RubyGem sprouts by name and version. Returns a RubyGem Gem Spec when installation is complete. If the installed gem has a Ruby file configured to ‘autorequire’, that file will also be required by this method so that any provided Ruby functionality will be immediately available to client scripts. If the installed gem contains a ‘sprout.spec’ file, any RemoteFileTargets will be resolved synchronously and those files will be available in the Sprout::Sprout.cache.

[Source]

# File sprout/lib/sprout.rb, line 292
    def self.sprout(name, version=nil)
      name = sprout_to_gem_name(name)
      gem_spec = self.find_gem_spec(name, version)
      sprout_spec_path = File.join(gem_spec.full_gem_path, @@spec)
      
      if(gem_spec.autorequire)
        $:.push(File.join(gem_spec.full_gem_path, 'lib'))
        require gem_spec.autorequire
      end
      if(File.exists?(sprout_spec_path))
        # Ensure the requisite files get downloaded and unpacked
        Builder.build(sprout_spec_path, gem_file_cache(gem_spec.name, gem_spec.version))
      else
        return gem_spec
      end
    end

Return the location on disk where this installation of Sprouts stores it‘s cached files. If the currently installed version of Sprouts were 0.7 and your system username were ‘foo’ this would return the following locations:

  • OSX /Users/foo/Library/Sprouts/cache/0.7
  • Windows C:/Documents And Settings/foo/Local Settings/Application Data/Sprouts/cache/0.7
  • Linux ~/.sprouts/cache/0.7

[Source]

# File sprout/lib/sprout.rb, line 329
    def self.sprout_cache
      @@sprout_cache ||= self.inferred_sprout_cache
    end

[Source]

# File sprout/lib/sprout.rb, line 333
    def self.sprout_cache=(cache)
      @@sprout_cache = cache
    end

Return sprout-#{name}-bundle for any name that does not begin with ‘sprout-’. This was used early on in development but should possibly be removed as we move forward and try to support arbitrary RubyGems.

[Source]

# File sprout/lib/sprout.rb, line 311
    def self.sprout_to_gem_name(name)
      if(!name.match(/^sprout-/))
        name = "sprout-#{name}-bundle"
      end
      return name
    end

[Validate]