Initial shot of "new" rake-based builder
Based on SVN https://swing.fit.cvut.cz/svn/stx/goodies/builder/trunk/rake@592
require 'rake'
require 'tsort'
require "#{File.dirname(__FILE__)}/dsl"
module Rake
end
module Rake::DSL
end
module Rake::Stx
end
module Rake::Stx::Configuration
def self.read_specs_from_dir(dir)
if File.exist? dir
info "Reading project specs from #{dir}..."
d = Dir.new(dir)
d.each do | file |
if file[0] != ?. and file =~ /.*\.rbspec$/
begin
info " #{file}"
$:.push(dir)
load dir / file
ensure
$:.pop()
end
end
end
d.each do | file |
if File.directory? File.join(dir, file)
read_specs_from_dir(File.join(dir, file)) if file[0] != ?.
end
end
end
end
def self.read_specs(project_dirs_spec)
project_dirs = ['specs' ,
ENV['HOME'] / '.smalltalk' / 'builder'
]
if win32?
project_dirs += project_dirs_spec.split(";")
else
project_dirs += project_dirs_spec.split(":")
end
project_dirs.each do | dir |
read_specs_from_dir(dir)
end
end
class ConfigurationException < Exception
end
class ConfigurationObject < Rake::StX::DSL::Object
NO_VALUE = Object.new()
attr_accessor :parent
attr_reader :last_repository
def initialize(name = nil, parent = nil)
@name = name
@parent = parent
@properties = {}
end
def name(name = NO_VALUE)
if (name != NO_VALUE)
@name = name
end
return @name
end
def group(*properties, &builder)
g = Group.new("<group>", self)
g._build(properties, &builder)
return g
end
def _build(properties, &builder)
if (properties and properties.size() > 0)
properties.last.each do | name, value |
self._set_property(name, caller(4).first, value )
end
end
if (builder != nil)
if (builder.arity == -1)
self.instance_exec &builder
elsif (builder.arity == 0)
self.instance_exec &builder
elsif (builder.arity == 1)
self.instance_exec(self,&builder)
else
raise ConfigurationException.new("invalid arity of builder block (0 or 1 arguments expected, #{builder.arity} given")
end
end
end
def accept_visitor(visitor)
raise Exception.new("Subclass responsibility (#{self.class.name}#accept_visitor")
end
end # class ConfigurationObject
class Project < ConfigurationObject
include Rake::DSL
PROJECTS = {}
attr_accessor :repositories, :packages, :tasks, :application
attr_accessor :last_repository
def self.named?(name)
if (PROJECTS.include?(name))
p = PROJECTS[name]
else
p = nil
end
return p
end
def self.named(name)
if (PROJECTS.include?(name))
p = PROJECTS[name]
else
p = self.new(name)
PROJECTS[name] = p
end
return p
end
def self.current()
if (not defined? @@Current)
@@Current = nil
end
return @@Current
end
def self.current=(project)
@@Current = project
end
property :app_name
property :app_version
def initialize(name)
super(name)
@imports = []
@repositories = []
@packages = []
@application = nil
@tasks = []
@last_repository = nil
end
def clone()
p = super()
p.repositories = @repositories.clone()
p.packages = @packages.clone()
return p
end
def packages_and_application()
app = self.application()
if app
return self.packages() + [ app ]
else
return self.packages()
end
end
class Import
def initialize(importee, imported)
@importee = importee
@imported = imported#as symbol!
@last_repository = importee.last_repository
end
def apply()
p = @importee.class.named(@imported)
p.apply_imports()
@importee.merge(p)
#merge repositories
p.repositories.each do | repo |
@importee.repository(repo.name).merge(repo)
end
#merge packages
begin
importee_last_repository = @importee.last_repository
@importee.last_repository = @last_repository
p.packages.each do | pkg |
if @importee.package? pkg.name
@importee.package(pkg.name).merge(pkg)
else
@importee.packages.push(pkg.clone())
end
pkg = @importee.package(pkg.name)
if (not pkg.repository and @last_repository_name)
pkg.set_property(:repository, @last_repository_name)
end
end
app = p.application
if app
if @importee.application
if @importee.application.parent == @importee
@importee.application.merge(app)
else
@importee.application = app.merge(@importee.application)
end
else
@importee.application = app
end
end
ensure
@importee.last_repository = importee_last_repository
end
p_tasks = p.tasks.collect { | t | t.clone }
@importee.tasks = p_tasks + @importee.tasks
info "Importing #{@imported}"
info " into #{@importee.name}"
end
end # class Import
def import(name)
@imports.push(Import.new(self, name))
end
def apply_imports()
@imports.each do | import |
import.apply()
end
end
def repository?(name)
if ((i = @repositories.index { | r | r.name == name }) != nil)
return @repositories[i]
else
return nil
end
end
def repository(name, *properties, &builder)
r = repository? name
if (r == nil)
r = Repository.new(name, self)
@repositories.push(r)
end
r._build(properties, &builder)
@last_repository = r
return r
end
def package?(name)
if ((i = @packages.index { | r | r.name == name }) != nil)
return @packages[i]
else
return nil
end
end
def package(name, *properties, &builder)
if (not (p = package?(name)))
p = Package.new(name, self)
@packages.push(p)
end
p._build(properties, &builder)
if (not p.repository and @last_repository)
p.repository = @last_repository.name
end
return p
end
def application(name = NO_VALUE, *properties, &builder)
return @application if name == NO_VALUE
warn "Refefining application for project #{self.name}!" if @application
if (not (p = package?(name)))
p = Application.new(name, self)
@application = p
end
p._build(properties, &builder)
if (not p.repository and @last_repository)
p.repository = @last_repository.name
end
return p
end
def tasks(*properties, &builder)
if (properties.empty? and not builder)
return @tasks
else
t = Tasks.new()
t._build(properties, &builder)
@tasks.push(t)
return t
end
end
def accept_visitor(visitor)
return visitor.visit_project(self)
end
end # class Configuration
class Tasks < ConfigurationObject
include Rake::TaskManager
def _build(*properties, &builder)
super(properties) {}
@builder = builder
end
def define!()
if (@builder != nil)
if (@builder.arity == -1)
@builder.call()
elsif (@builder.arity == 0)
@builder.call()
elsif (@builder.arity == 1)
@builder.call(self)
else
raise ConfigurationException.new("invalid arity of builder block (0 or 1 arguments expected, #{builder.arity} given")
end
end
end
def accept_visitor(visitor)
return visitor.visit_tasks(self)
end
end # class Tasks
class Package < ConfigurationObject
property :repository, :class => Symbol
property :branch
property :link, :values => [ true, false ], :default => true
property :test, :values => [ true, false ], :default => false
property :coverage, :values => [ true, false ], :default => false
property :lint, :values => [ true, false ], :default => false
property :checkout, :default => (Proc.new do | pkg |
info "Checking out #{pkg.name}..."
checkout pkg.repository, pkg.directory,
:branch => pkg.branch, :package => pkg, :separator => pkg._separator
end), :class => Proc
property :update, :default => (Proc.new do | pkg |
info "Updating #{pkg.name}..."
update pkg.repository, pkg.directory,
:branch => pkg.branch, :package => pkg, :separator => pkg._separator
end), :class => Proc
property :stc_flags, :default => '+optinline +optinline2 -optContext', :class => String
def _separator()
return (self.parent.repository(self.repository)).separator
end
def name_components()
if not @name_components then
@name_components = self.name.sub(":", '/').split('/')
end
return @name_components
end
def top()
# Answers relative path to top directory
return File.join(self.name_components().collect { | e | '..' })
end
def directory()
return File.join(*self.name_components)
end
def dll_name_without_suffix()
return "lib#{self.directory.gsub(File::SEPARATOR, "_")}"
end
def dll_name()
case
when win32?
return "#{dll_name_without_suffix()}.dll"
when unix?
return"#{dll_name_without_suffix()}.so"
else
error_unsupported_platform
end
end
def nested_package?
project.packages.each do | each |
if self.name != each.name and self.name.start_with? each.name + '/'
return true
end
end
return false
end
def parent_package
return nil if not nested_package?
last_slash = self.name.rindex(?/)
return nil if last_slash == nil
parent_package_name = self.name.slice(0, last_slash)
project.packages.each do | each |
return each if each.name == parent_package_name
end
return nil
end
def application?
return false
end
def accept_visitor(visitor)
return visitor.visit_package(self)
end
end # class Package
class Application < Package
def executable
c = self.name_components
nm = c.last
if win32?
#JV@2011-07-22: HACK for Windows, since smalltalk.bat messes the argument list!!!
if self.name == 'stx:projects/smalltalk'
return 'stx'
end
end
if nm == 'application'
nm = c[c.size - 2]
end
return nm
end
def application?
return true
end
def accept_visitor(visitor)
return visitor.visit_application(self)
end
end # class Application
class Group < ConfigurationObject
def initialize(name, parent)
super(name, parent)
@properties_to_apply = Properties.new('<properties>', self);
@nested_entities = []
end
def properties(*properties, &builder)
@properties_to_apply._build(properties, &builder)
end
def _build(*properties, &builder)
@properties_to_apply._build(properties)
super([], &builder)
@nested_entities.each do | each |
@properties_to_apply.properties.each do | name, value |
each.set_property(name, value, caller(2).first)
end
end
end
def method_missing(selector, *args, &block)
e = parent.send(selector,*args, &block)
@nested_entities.push(e)
return e
end
class Properties < ConfigurationObject
end # class Group::Properties
end # class Group
class Repository < ConfigurationObject
property :type, :class => Symbol
property :url
property :separator, :default => '.'
def accept_visitor(visitor)
return visitor.visit_repository(self)
end
end # class Repository
class Visitor
def visit(object)
return object.accept_visitor(self)
end
def visit_project(project)
project.repositories.each { | repo | self.visit(repo) }
project.packages.each { | repo | self.visit(repo) }
project.tasks.each { | repo | self.visit(repo) }
self.visit(project.application) if project.application
end
def visit_tasks(tasks)
end
def visit_repository(tasks)
end
def visit_package(tasks)
end
def visit_application(app)
return self.visit_package(app)
end
end
class Printer < Visitor
attr_accessor :stream
def print_property(prop, overridden = false)
if overridden
puts "# overrides #{prop.value}"
puts "# defined at #{prop.source}"
else
puts "#{prop.name} #{prop.value}"
puts "# (defined at #{prop.source})"
end
print_property(prop.overridden, true) if prop.overridden
end
def print_properties(obj)
obj.properties.each do | name, prop |
print_property(prop)
end
end
def initialize()
@indent = 0
@stream = $stdout
end
def puts(*args)
args.each do | arg |
@indent.times { | i | @stream.write( " " )}
@stream.puts(arg)
end
end
def indent(increment = 1)
@indent += increment
yield
@indent -= increment
end
def visit_repository(repo)
puts "repository :'#{repo.name}' do"
indent do
print_properties(repo)
end
puts "end"
end
def visit_package(pkg)
puts "#{pkg.application? ? 'application' : 'package'} \"#{pkg.name}\" do"
if pkg.nested_package?
puts " # nested package"
p = pkg.parent_package
if p == nil
puts " # OOPS, parent package not found!"
else
puts " # parent package: #{p.name}"
end
end
indent do
print_properties(pkg)
end
puts 'end'
end
def visit_project(project)
puts "project :'#{project.name}' do"
indent do
print_properties(project)
super(project)
end
puts "end"
end
end
end # module Rake::StX::Configuration
def projects()
return Rake::Stx::Configuration::Project::PROJECTS
end
def project(name = Rake::Stx::Configuration::ConfigurationObject::NO_VALUE, *properties, &builder)
if (name == Rake::Stx::Configuration::ConfigurationObject::NO_VALUE)
return Rake::Stx::Configuration::Project.current()
else
p = Rake::Stx::Configuration::Project.named(name)
p._build(properties, &builder)
return p
end
end
def project!(name)
if (p = Rake::Stx::Configuration::Project.named?(name))
Rake::Stx::Configuration::Project.current = p
return p
else
raise Rake::Stx::Configuration::ConfigurationException.new("No such project (#{name})")
end
end