about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorzedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9>2006-11-25 01:10:57 +0000
committerzedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9>2006-11-25 01:10:57 +0000
commit7f17c43dab3a257d040ab66617b98c7f1cafc2b8 (patch)
tree9f4de223c0acbbfc2a4a6ac08260f3e552985e1c
parent7bbe221d778d8b46af3c3ea3135a72f0d4d7362d (diff)
downloadunicorn-7f17c43dab3a257d040ab66617b98c7f1cafc2b8.tar.gz
git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@407 19e92222-5c0b-0410-8929-a290d50e31e9
-rw-r--r--projects/mongrel_service/CHANGELOG5
-rw-r--r--projects/mongrel_service/README4
-rw-r--r--projects/mongrel_service/Rakefile71
-rw-r--r--projects/mongrel_service/TODO7
-rw-r--r--projects/mongrel_service/bin/mongrel_service156
-rw-r--r--projects/mongrel_service/lib/mongrel_service/init.rb142
-rw-r--r--projects/mongrel_service/native/_debug.bi60
-rw-r--r--projects/mongrel_service/native/mongrel_service.bas149
-rw-r--r--projects/mongrel_service/native/mongrel_service.bi50
-rw-r--r--projects/mongrel_service/native/process.bas284
-rw-r--r--projects/mongrel_service/native/process.bi48
-rw-r--r--projects/mongrel_service/tools/freebasic.rb336
12 files changed, 1060 insertions, 252 deletions
diff --git a/projects/mongrel_service/CHANGELOG b/projects/mongrel_service/CHANGELOG
new file mode 100644
index 0000000..33c0dd8
--- /dev/null
+++ b/projects/mongrel_service/CHANGELOG
@@ -0,0 +1,5 @@
+*SVN*
+    * Single Service (SingleMongrel) object type implemented.
+    * Updated Rakefile to reflect the new building steps.
+    * Removed SendSignal, too hackish for my taste, replaced with complete FB solution.
+     \ No newline at end of file
diff --git a/projects/mongrel_service/README b/projects/mongrel_service/README
index 313d9a9..45bf9c8 100644
--- a/projects/mongrel_service/README
+++ b/projects/mongrel_service/README
@@ -5,9 +5,7 @@ It will work like before, with this this syntax when calling mongrel_rails:
 
 service::install
 service::remove
-service::start
-service::stop
-service::status
+service::update
 
 = Author:
   Luis Lavena
diff --git a/projects/mongrel_service/Rakefile b/projects/mongrel_service/Rakefile
index 7a4bb8d..dcab8c7 100644
--- a/projects/mongrel_service/Rakefile
+++ b/projects/mongrel_service/Rakefile
@@ -4,6 +4,7 @@ require 'rake/clean'
 require 'rake/gempackagetask'
 require 'rake/rdoctask'
 require 'tools/rakehelp'
+require 'tools/freebasic'
 require 'fileutils'
 include FileUtils
 
@@ -13,27 +14,79 @@ setup_clean ["pkg", "lib/*.bundle", "*.gem", ".config"]
 setup_rdoc ['README', 'LICENSE', 'COPYING', 'lib/**/*.rb', 'doc/**/*.rdoc']
 
 desc "Does a full compile, test run"
-task :default => [:test, :package]
+task :default => [:test, :compile, :package]
 
-version="0.2.1"
-name="mongrel_service"
+GEM_VERSION = "0.3.0"
+GEM_NAME = "mongrel_service"
 
-setup_gem(name, version) do |spec|
+# ServiceFB namespace (lib)
+namespace :lib do
+  project_task 'servicefb' do
+    lib       'ServiceFB'
+    build_to  'lib'
+
+    define    'SERVICEFB_DEBUG_LOG' if ENV['DEBUG_LIB']
+    source    'lib/ServiceFB/ServiceFB.bas'
+  end
+  
+  project_task 'servicefb_utils' do
+    lib       'ServiceFB_Utils'
+    build_to  'lib'
+
+    define    'SERVICEFB_DEBUG_LOG' if ENV['DEBUG_LIB']
+    source    'lib/ServiceFB/ServiceFB_Utils.bas'
+  end
+end
+# add lib namespace to global tasks
+#include_projects_of :lib
+task :compile => "lib:build"
+task :clobber => "lib:clobber"
+
+# mongrel_service (native)
+namespace :native do
+  project_task  'mongrel_service' do
+    executable  'mongrel_service'
+    build_to    'bin'
+    
+    define      'DEBUG_LOG' if ENV['DEBUG_LOG']
+    
+    main        'native/mongrel_service.bas'
+    source      'native/process.bas'
+    
+    # including the precompiled file show warnings when linking with
+    # ld, due special m$ directives in the obj file
+    # will solve that later, when migrate the asm part of code to gcc
+    # source      'native/send_signal.o'
+    
+    lib_path    'lib'
+    library     'ServiceFB', 'ServiceFB_Utils'
+    # library     'send_signal'
+    library     'user32', 'advapi32', 'psapi'
+  end
+end
+#include_projects_of :native
+task :compile => "native:build"
+task :clobber => "native:clobber"
+
+
+setup_gem(GEM_NAME, GEM_VERSION) do |spec|
   spec.summary = "Mongrel Native Win32 Service Plugin for Rails"
   spec.description = "This plugin offer native win32 services for rails, powered by Mongrel."
-  spec.author="Luis Lavena"
+  spec.author = "Luis Lavena"
+  spec.platform = Gem::Platform::WIN32
   
-  # added mongrel_service executable
-  spec.executables = ["mongrel_service"]
+  spec.files -= Dir["bin/**/*"]
+  spec.files -= Dir["**/*.{o,a}"]
+  spec.files += Dir["native/**/*.{bas,bi}"]
+  spec.files += ["bin/mongrel_service.exe"]
   
   spec.add_dependency('gem_plugin', '>= 0.2.1')
   spec.add_dependency('mongrel', '>= 0.3.12.4')
   spec.add_dependency('win32-service', '>= 0.5.0')
-
+  
   spec.files += Dir.glob("resources/**/*")
 end
 
-
 task :install => [:test, :package] do
   sh %{gem install pkg/#{name}-#{version}.gem}
 end
diff --git a/projects/mongrel_service/TODO b/projects/mongrel_service/TODO
new file mode 100644
index 0000000..66c3d0d
--- /dev/null
+++ b/projects/mongrel_service/TODO
@@ -0,0 +1,7 @@
+= mongrel_service (native) TODO List
+
+  * Add more documentation about services
+  * Build win32/service extension inside the gem and ship with it.
+    (instead of relying in the non-official 0.5.0 one).
+  * Sanitize SingleMongrel and document the functions
+   \ No newline at end of file
diff --git a/projects/mongrel_service/bin/mongrel_service b/projects/mongrel_service/bin/mongrel_service
deleted file mode 100644
index 64e4ed1..0000000
--- a/projects/mongrel_service/bin/mongrel_service
+++ /dev/null
@@ -1,156 +0,0 @@
-require 'rubygems'
-require 'win32/service'
-require 'mongrel'
-require 'mongrel/rails'
-require 'optparse'
-require 'yaml'
-
-# Avoid curious users from running this script from command line
-if ENV["HOMEDRIVE"]!=nil
-  puts "mongrel_service is not designed to run form commandline,"
-  puts "please use mongrel_rails service:: commands to create a win32 service."
-  exit
-end
-
-# We need to use OpenProcess and SetProcessAffinityMask on WinNT/2K/XP for
-# binding the process to each cpu.
-# Kernel32 Module Just for Win32 :D
-require 'dl/win32'
-
-module Kernel32
-  [
-    %w/OpenProcess            LLL    L/,
-    %w/SetProcessAffinityMask LL     L/,
-  ].each do |fn|
-    const_set fn[0].intern, Win32API.new('kernel32.dll', *fn)
-  end
-  
-  PROCESS_ALL_ACCESS = 0x1f0fff
-  
-  module_function
-
-  def set_affinity(pid, cpu)
-    handle = OpenProcess.call(PROCESS_ALL_ACCESS, 0, pid)
-  
-    # CPU mask is a bit weird, hehehe :)
-    # default mask for CPU 1
-    mask = 1
-    mask = %w{1 2 4 8 16 32 64 128}[cpu.to_i - 1] if cpu.to_i.between?(1, 8)
-    
-    SetProcessAffinityMask.call(handle, mask.to_i)
-  end
-end
-# End Kernel32 Module
-
-# RailsService code here
-class RailsService < Win32::Daemon
-  def initialize(settings)
-    @settings = settings
-  end
-  
-  def service_init
-    @config = Mongrel::Rails::RailsConfigurator.new(@settings) do
-      log "Starting Mongrel in #{defaults[:environment]} mode at #{defaults[:host]}:#{defaults[:port]}"
-
-      listener do
-        mime = {}
-        if defaults[:mime_map]
-          log "Loading additional MIME types from #{defaults[:mime_map]}"
-          mime = load_mime_map(defaults[:mime_map], mime)
-        end
-
-        if defaults[:debug]
-          log "Installing debugging prefixed filters.  Look in log/mongrel_debug for the files."
-          debug "/"
-        end
-
-        log "Starting Rails in environment #{defaults[:environment]} ..."
-        log "Mounting Rails at #{defaults[:prefix]}..." if defaults[:prefix]
-        uri defaults[:prefix] || "/", :handler => rails(:mime => mime, :prefix => defaults[:prefix])
-        log "Rails loaded."
-
-        #log "Loading any Rails specific GemPlugins"
-        #load_plugins
-
-        if defaults[:config_script]
-          log "Loading #{defaults[:config_script]} external config script"
-          run_config(defaults[:config_script])
-        end
-      end
-    end
-
-  end
-  
-  def service_main
-    @config.run
-    @config.log "Mongrel available at #{@settings[:host]}:#{@settings[:port]}"
-
-    while state == RUNNING
-      sleep 1
-    end
-
-                @config.stop
-  end
-  
-end
-
-# default options
-OPTIONS = {
-  :environment  => ENV['RAILS_ENV'] || "development",
-  :port         => 3000,
-  :host         => "0.0.0.0",
-  :log_file     => "log/mongrel.log",
-  :pid_file     => "log/mongrel.pid",
-  :num_procs    => 1024,
-  :timeout      => 0,
-  :mime_map     => nil,
-  :cwd          => Dir.pwd,
-  :docroot      => "public",
-  :debug        => false,
-  :config_file  => nil,
-  :config_script => nil,
-  :cpu          => nil,
-  :prefix       => nil
-}
-
-ARGV.options do |opts|
-  opts.on('-e', '--environment ENV', "Rails environment to run as") { |OPTIONS[:environment]| }
-  opts.on('-p', '--port PORT', "Which port to bind to") { |OPTIONS[:port]| }
-  opts.on('-a', '--address ADDR', "Address to bind to") { |OPTIONS[:host]| }
-  opts.on('-l', '--log FILE', "Where to write log messages") { |OPTIONS[:log_file]| }
-  opts.on('-P', '--pid FILE', "Where to write the PID") { |OPTIONS[:pid_file]| }
-  opts.on('-n', '--num-procs INT', "Number of processors active before clients denied") { |OPTIONS[:num_procs]| }
-  opts.on('-t', '--timeout TIME', "Timeout all requests after 100th seconds time") { |OPTIONS[:timeout]| }
-  opts.on('-m', '--mime PATH', "A YAML file that lists additional MIME types") { |OPTIONS[:mime_map]| }
-  opts.on('-c', '--chdir PATH', "Change to dir before starting (will be expanded)") { |OPTIONS[:cwd]| }
-  opts.on('-r', '--root PATH', "Set the document root (default 'public')") { |OPTIONS[:docroot]| }
-  opts.on('-B', '--debug', "Enable debugging mode") { |OPTIONS[:debug]| }
-  opts.on('-C', '--config FILE', "Use a config file") { |OPTIONS[:config_file]| }
-  opts.on('-S', '--script FILE', "Load the given file as an extra config script.") { |OPTIONS[:config_script]| }
-  opts.on('-u', '--cpu CPU', "Bind the process to specific cpu, starting from 1.") { |OPTIONS[:cpu]| }
-  opts.on('--prefix PATH', "URL prefix for Rails app") { |OPTIONS[:prefix]| }
-
-  opts.parse!
-end
-
-# We must bind to a specific cpu?
-if OPTIONS[:cpu]
-  Kernel32.set_affinity(Process.pid, OPTIONS[:cpu])
-end
-
-# expand path
-OPTIONS[:cwd] = File.expand_path(OPTIONS[:cwd])
-
-# chdir to that path
-Dir.chdir(OPTIONS[:cwd])
-
-# redirecting STDERR to a file so we could trace it.
-STDERR.reopen("log/service.log", "w")
-STDERR.sync = true
-
-#FIXME: I commented this because fails during load_plugins from configurator, we need to fix
-#GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE
-
-# initialize the daemon and pass control to win32/service
-svc = RailsService.new(OPTIONS)
-svc.mainloop
diff --git a/projects/mongrel_service/lib/mongrel_service/init.rb b/projects/mongrel_service/lib/mongrel_service/init.rb
index 2ef96d3..c6fea0e 100644
--- a/projects/mongrel_service/lib/mongrel_service/init.rb
+++ b/projects/mongrel_service/lib/mongrel_service/init.rb
@@ -3,7 +3,7 @@ require 'mongrel'
 require 'mongrel/rails'
 require 'rbconfig'
 require 'win32/service'
-
+require 'fileutils'
 
 module Service
   class Install < GemPlugin::Plugin "/commands"
@@ -26,7 +26,6 @@ module Service
           ['-B', '--debug', "Enable debugging mode", :@debug, false],
           ['-C', '--config PATH', "Use a config file", :@config_file, nil],
           ['-S', '--script PATH', "Load the given file as an extra config script.", :@config_script, nil],
-          ['-u', '--cpu CPU', "Bind the process to specific cpu, starting from 1.", :@cpu, nil],
           ['', '--prefix PATH', "URL prefix for Rails app", :@prefix, nil]
         ]
     end
@@ -43,7 +42,7 @@ module Service
   
       # start with the premise of app really exist.
       app_exist = true
-      %w{app config db log public}.each do |path|
+      %w{app config log}.each do |path|
         if !File.directory?(File.join(@cwd, path))
           app_exist = false
           break
@@ -60,9 +59,6 @@ module Service
       valid_exists? @mime_map, "MIME mapping file does not exist: #@mime_map" if @mime_map
       valid_exists? @config_file, "Config file not there: #@config_file" if @config_file
 
-      # Validate the number of cpu to bind to.
-      valid? @cpu.to_i > 0, "You must specify a numeric value for cpu. (1..8)" if @cpu
-      
       # We should validate service existance here, right Zed?
       begin
         valid? !Win32::Service.exists?(@svc_name), "The service already exist, please remove it first."
@@ -78,45 +74,69 @@ module Service
     end
     
     def run
+      # check if mongrel_service.exe is in ruby bindir.
+      gem_root = File.join(File.dirname(__FILE__), "..", "..")
+      gem_executable = File.join(gem_root, "bin/mongrel_service.exe")
+      bindir_executable = File.join(Config::CONFIG['bindir'], '/mongrel_service.exe')
+      
+      unless File.exist?(bindir_executable)
+        STDERR.puts "** Copying native mongrel_service executable..."
+        FileUtils.cp gem_executable, bindir_executable rescue nil
+      end
+      
+      unless FileUtils.compare_file(bindir_executable, gem_executable)
+        STDERR.puts "** Updating native mongrel_service executable..."
+        FileUtils.rm_f bindir_executable rescue nil
+        FileUtils.cp gem_executable, bindir_executable rescue nil
+      end
+      
+      # build the command line
+      argv = []
+      
+      # start using the native executable
+      argv << '"' + bindir_executable + '"'
+      
+      # use the 'single' service for now
+      argv << "single"
+      
       # command line setting override config file settings
       @options = { :host => @address,  :port => @port, :cwd => @cwd,
         :log_file => @log_file, :pid_file => @pid_file, :environment => @environment,
         :docroot => @docroot, :mime_map => @mime_map,
         :debug => @debug, :includes => ["mongrel"], :config_script => @config_script,
         :num_procs => @num_procs, :timeout => @timeout, :cpu => @cpu, :prefix => @prefix
-
       }
-
+      
+      # if we are using a config file, pass -c and -C to the service instead of each start parameter.
       if @config_file
-        STDERR.puts "** Loading settings from #{@config_file} (command line options override)."
+        STDERR.puts "** Using #{@config_file} instead of command line parameters."
         conf = YAML.load_file(@config_file)
-        @options = @options.merge! conf
+        
+        # add the root folder (-c)
+        argv << "-c \"#{conf[:cwd]}\""
+        
+        # use the config file
+        argv << "-C \"#{@config_file}\""
+
+      else
+        # use the command line instead
+        # now the options
+        argv << "-e #{@options[:environment]}" if @options[:environment]
+        argv << "-p #{@options[:port]}"
+        argv << "-a #{@options[:host]}"  if @options[:host]
+        argv << "-l \"#{@options[:log_file]}\"" if @options[:log_file]
+        argv << "-P \"#{@options[:pid_file]}\""
+        argv << "-c \"#{@options[:cwd]}\"" if @options[:cwd]
+        argv << "-t #{@options[:timeout]}" if @options[:timeout]
+        argv << "-m \"#{@options[:mime_map]}\"" if @options[:mime_map]
+        argv << "-r \"#{@options[:docroot]}\"" if @options[:docroot]
+        argv << "-n #{@options[:num_procs]}" if @options[:num_procs]
+        argv << "-B" if @options[:debug]
+        argv << "-S \"#{@options[:config_script]}\"" if @options[:config_script]
+        argv << "-u #{@options[:cpu.to_i]}" if @options[:cpu]
+        argv << "--prefix \"#{@options[:prefix]}\"" if @options[:prefix]
       end
 
-      argv = []
-      
-      # ruby.exe instead of rubyw.exe due a exception raised when stoping the service!
-      argv << '"' + Config::CONFIG['bindir'] + '/ruby.exe' + '"'
-      
-      # add service_script, we now use the rubygem powered one
-      argv << '"' + Config::CONFIG['bindir'] + '/mongrel_service' + '"'
-
-      # now the options
-      argv << "-e #{@options[:environment]}" if @options[:environment]
-      argv << "-p #{@options[:port]}"
-      argv << "-a #{@options[:host]}"  if @options[:host]
-      argv << "-l \"#{@options[:log_file]}\"" if @options[:log_file]
-      argv << "-P \"#{@options[:pid_file]}\""
-      argv << "-c \"#{@options[:cwd]}\"" if @options[:cwd]
-      argv << "-t #{@options[:timeout]}" if @options[:timeout]
-      argv << "-m \"#{@options[:mime_map]}\"" if @options[:mime_map]
-      argv << "-r \"#{@options[:docroot]}\"" if @options[:docroot]
-      argv << "-n #{@options[:num_procs]}" if @options[:num_procs]
-      argv << "-B" if @options[:debug]
-      argv << "-S \"#{@options[:config_script]}\"" if @options[:config_script]
-      argv << "-u #{@options[:cpu.to_i]}" if @options[:cpu]
-      argv << "--prefix \"#{@options[:prefix]}\"" if @options[:prefix]
-
       svc = Win32::Service.new
       begin
         svc.create_service{ |s|
@@ -124,6 +144,7 @@ module Service
           s.display_name     = @svc_display
           s.binary_path_name = argv.join ' '
           s.dependencies     = []
+          s.service_type     = Win32::Service::WIN32_OWN_PROCESS
         }
         puts "Mongrel service '#{@svc_display}' installed as '#{@svc_name}'."
       rescue Win32::ServiceError => err
@@ -147,6 +168,9 @@ module Service
       # Validate that the service exists
       begin
         valid? Win32::Service.exists?(@svc_name), "There is no service with that name, cannot proceed."
+        Win32::Service.open(@svc_name) do |svc|
+          valid? svc.binary_path_name.include?("mongrel_service"), "The service specified isn't a Mongrel service."
+        end
       rescue
       end
       
@@ -172,54 +196,4 @@ module Service
       puts "#{display_name} service removed."
     end
   end
-
-  class Start < GemPlugin::Plugin "/commands"
-    include Mongrel::Command::Base
-    include ServiceValidation
-    
-    def run
-      display_name = Win32::Service.getdisplayname(@svc_name)
-  
-      begin
-        Win32::Service.start(@svc_name)
-        started = false
-        while started == false
-          s = Win32::Service.status(@svc_name)
-          started = true if s.current_state == "running"
-          break if started == true
-          puts "One moment, " + s.current_state
-          sleep 1
-        end
-        puts "#{display_name} service started"
-      rescue Win32::ServiceError => err
-        puts "There was a problem starting the service:"
-        puts err
-      end
-    end
-  end
-
-  class Stop < GemPlugin::Plugin "/commands"
-    include Mongrel::Command::Base
-    include ServiceValidation
-    
-    def run
-      display_name = Win32::Service.getdisplayname(@svc_name)
-  
-      begin
-        Win32::Service.stop(@svc_name)
-        stopped = false
-        while stopped == false
-          s = Win32::Service.status(@svc_name)
-          stopped = true if s.current_state == "stopped"
-          break if stopped == true
-          puts "One moment, " + s.current_state
-          sleep 1
-        end
-        puts "#{display_name} service stopped"
-      rescue Win32::ServiceError => err
-        puts "There was a problem stopping the service:"
-        puts err
-      end
-    end
-  end
 end
diff --git a/projects/mongrel_service/native/_debug.bi b/projects/mongrel_service/native/_debug.bi
new file mode 100644
index 0000000..e0f9592
--- /dev/null
+++ b/projects/mongrel_service/native/_debug.bi
@@ -0,0 +1,60 @@
+'##################################################################
+'#
+'# mongrel_service: Win32 native implementation for mongrel
+'#                  (using ServiceFB and FreeBASIC)
+'#
+'# Copyright (c) 2006 Multimedia systems
+'# (c) and code by Luis Lavena
+'# Portions (c) Louis Thomas
+'#
+'#  mongrel_service (native) and mongrel_service gem_pluing are licensed
+'#  in the same terms as mongrel, please review the mongrel license at
+'#  http://mongrel.rubyforge.org/license.html
+'#  
+'##################################################################
+
+'##################################################################
+'# Requirements:
+'# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006).
+'#
+'##################################################################
+
+#ifndef __Debug_bi__
+#define __Debug_bi__
+
+#ifdef DEBUG_LOG
+    #include once "vbcompat.bi"
+    #ifndef DEBUG_LOG_FILE
+        #define DEBUG_LOG_FILE EXEPATH + "\debug.log"
+    #endif
+
+    '# this procedure is only used for debugging purposed, will be removed from
+    '# final compilation
+    private sub debug_to_file(byref message as string, byref file as string, byval linenumber as uinteger, byref func as string)
+        dim handle as integer
+        static first_time as integer
+        
+        handle = freefile
+        open DEBUG_LOG_FILE for append as #handle
+        
+        if (first_time = 0) then
+            print #handle, "# Logfile created on "; format(now(), "dd/mm/yyyy HH:mm:ss")
+            print #handle, ""
+            first_time = 1
+        end if
+        
+        '# src/module.bas:123, namespace.function:
+        '#   message
+        '#
+        print #handle, file; ":"; str(linenumber); ", "; lcase(func); ":"
+        print #handle, space(2); message
+        print #handle, ""
+        
+        close #handle
+    end sub
+    #define debug(message) debug_to_file(message, __FILE__, __LINE__, __FUNCTION__)
+#else
+    #define debug(message)
+#endif '# DEBUG_LOG
+
+#endif '# __Debug_bi__
diff --git a/projects/mongrel_service/native/mongrel_service.bas b/projects/mongrel_service/native/mongrel_service.bas
new file mode 100644
index 0000000..9a3f002
--- /dev/null
+++ b/projects/mongrel_service/native/mongrel_service.bas
@@ -0,0 +1,149 @@
+'##################################################################
+'#
+'# mongrel_service: Win32 native implementation for mongrel
+'#                  (using ServiceFB and FreeBASIC)
+'#
+'# Copyright (c) 2006 Multimedia systems
+'# (c) and code by Luis Lavena
+'# Portions (c) Louis Thomas
+'#
+'#  mongrel_service (native) and mongrel_service gem_pluing are licensed
+'#  in the same terms as mongrel, please review the mongrel license at
+'#  http://mongrel.rubyforge.org/license.html
+'#  
+'##################################################################
+
+'##################################################################
+'# Requirements:
+'# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006).
+'#
+'##################################################################
+
+#include once "mongrel_service.bi"
+#define DEBUG_LOG_FILE EXEPATH + "\mongrel_service.log"
+#include once "_debug.bi"
+
+namespace mongrel_service
+    using fb.process
+    
+    constructor SingleMongrel()
+        with this.__service
+            .name = "single"
+            .description = "Mongrel Single Process service"
+            
+            '# disabling shared process
+            .shared_process = FALSE
+            
+            '# TODO: fix inheritance here
+            .onInit = @single_onInit
+            .onStart = @single_onStart
+            .onStop = @single_onStop
+        end with
+        
+        '# TODO: fix inheritance here
+        single_mongrel_ref = @this
+    end constructor
+    
+    destructor SingleMongrel()
+        '# TODO: fin inheritance here
+    end destructor
+    
+    function single_onInit(byref self as ServiceProcess) as integer
+        dim result as integer
+        dim mongrel_cmd as string
+        
+        debug("single_onInit()")
+        
+        '# ruby.exe must be in the path, which we guess is already there.
+        '# because mongrel_service executable (.exe) is located in the same
+        '# folder than mongrel_rails ruby script, we complete the path with
+        '# EXEPATH + "\mongrel_rails" to make it work.
+        mongrel_cmd = "ruby.exe " + EXEPATH + "\mongrel_rails start"
+        
+        '# due lack of inheritance, we use single_mongrel_ref as pointer to
+        '# SingleMongrel instance. now we should call StillAlive
+        self.StillAlive()
+        if (len(self.commandline) > 0) then
+            '# fix commandline, it currently contains params to be passed to
+            '# mongrel_rails, and not ruby.exe nor the script to be run.
+            self.commandline = mongrel_cmd + " " + self.commandline
+            
+            '# now launch the child process
+            debug("starting child process with cmdline: " + self.commandline)
+            single_mongrel_ref->__child_pid = 0
+            single_mongrel_ref->__child_pid = Spawn(self.commandline)
+            self.StillAlive()
+            
+            '# check if pid is valid
+            if (single_mongrel_ref->__child_pid > 0) then
+                '# it worked
+                debug("child process pid: " + str(single_mongrel_ref->__child_pid))
+                result = not FALSE
+            end if
+        else
+            '# if no param, no service!
+            debug("no parameters was passed to this service!")
+            result = FALSE
+        end if
+        
+        debug("single_onInit() done")
+        return result
+    end function
+    
+    sub single_onStart(byref self as ServiceProcess)
+        debug("single_onStart()")
+        
+        do while (self.state = Running) or (self.state = Paused)
+            sleep 100
+        loop
+        
+        debug("single_onStart() done")
+    end sub
+    
+    sub single_onStop(byref self as ServiceProcess)
+        debug("single_onStop()")
+        
+        '# now terminates the child process
+        if not (single_mongrel_ref->__child_pid = 0) then
+            debug("trying to kill pid: " + str(single_mongrel_ref->__child_pid))
+            'if not (send_break(single_mongrel_ref->__child_pid) = 0) then
+            if not (Terminate(single_mongrel_ref->__child_pid) = TRUE) then
+                debug("Terminate() reported a problem when terminating process " + str(single_mongrel_ref->__child_pid))
+            else
+                debug("child process terminated with success.")
+                single_mongrel_ref->__child_pid = 0
+            end if
+        end if
+        
+        debug("single_onStop() done")
+    end sub
+    
+    sub application()
+        dim simple as SingleMongrel
+        dim host as ServiceHost
+        dim ctrl as ServiceController = ServiceController("Mongrel Win32 Service", "version 0.3.0", _
+                                                            "(c) 2006 The Mongrel development team.")
+        
+        '# add SingleMongrel (service)
+        host.Add(simple.__service)
+        select case ctrl.RunMode()
+            '# call from Service Control Manager (SCM)
+            case RunAsService:
+                debug("ServiceHost RunAsService")
+                host.Run()
+                
+            '# call from console, useful for debug purposes.
+            case RunAsConsole:
+                debug("ServiceController Console")
+                ctrl.Console()
+                
+            case else:
+                ctrl.Banner()
+                print "mongrel_service is not designed to run form commandline,"
+                print "please use mongrel_rails service:: commands to create a win32 service."
+        end select
+    end sub
+end namespace
+
+'# MAIN: start native mongrel_service here
+mongrel_service.application()
diff --git a/projects/mongrel_service/native/mongrel_service.bi b/projects/mongrel_service/native/mongrel_service.bi
new file mode 100644
index 0000000..a4d1f6b
--- /dev/null
+++ b/projects/mongrel_service/native/mongrel_service.bi
@@ -0,0 +1,50 @@
+'##################################################################
+'#
+'# mongrel_service: Win32 native implementation for mongrel
+'#                  (using ServiceFB and FreeBASIC)
+'#
+'# Copyright (c) 2006 Multimedia systems
+'# (c) and code by Luis Lavena
+'# Portions (c) Louis Thomas
+'#
+'#  mongrel_service (native) and mongrel_service gem_pluing are licensed
+'#  in the same terms as mongrel, please review the mongrel license at
+'#  http://mongrel.rubyforge.org/license.html
+'#  
+'##################################################################
+
+'##################################################################
+'# Requirements:
+'# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006).
+'#
+'##################################################################
+
+#define SERVICEFB_INCLUDE_UTILS
+#include once "lib/ServiceFB/ServiceFB.bi"
+#include once "process.bi"
+
+namespace mongrel_service
+    using fb.svc
+    using fb.svc.utils
+    
+    declare function single_onInit(byref as ServiceProcess) as integer
+    declare sub single_onStart(byref as ServiceProcess)
+    declare sub single_onStop(byref as ServiceProcess)
+    
+    '# SingleMongrel
+    type SingleMongrel
+        declare constructor()
+        declare destructor()
+        
+        '# TODO: replace for inheritance here
+        'declare function onInit() as integer
+        'declare sub onStart()
+        'declare sub onStop()
+        
+        __service       as ServiceProcess
+        __child_pid     as uinteger
+    end type
+    
+    '# TODO: replace with inheritance here
+    dim shared single_mongrel_ref as SingleMongrel ptr
+end namespace
diff --git a/projects/mongrel_service/native/process.bas b/projects/mongrel_service/native/process.bas
new file mode 100644
index 0000000..396ea2a
--- /dev/null
+++ b/projects/mongrel_service/native/process.bas
@@ -0,0 +1,284 @@
+'##################################################################
+'#
+'# mongrel_service: Win32 native implementation for mongrel
+'#                  (using ServiceFB and FreeBASIC)
+'#
+'# Copyright (c) 2006 Multimedia systems
+'# (c) and code by Luis Lavena
+'# Portions (c) Louis Thomas
+'#
+'#  mongrel_service (native) and mongrel_service gem_pluing are licensed
+'#  in the same terms as mongrel, please review the mongrel license at
+'#  http://mongrel.rubyforge.org/license.html
+'#  
+'##################################################################
+
+'##################################################################
+'# Requirements:
+'# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006).
+'#
+'##################################################################
+
+#include once "process.bi"
+#define DEBUG_LOG_FILE EXEPATH + "\mongrel_service.log"
+#include once "_debug.bi"
+
+namespace fb
+namespace process
+    '# Spawn(cmdline) will try to create a new process, monitor
+    '# if it launched successfuly (5 seconds) and then return the
+    '# new children PID (Process IDentification) or 0 in case of problems
+    function Spawn(byref cmdline as string) as uinteger
+        dim result as uinteger
+        dim success as BOOL
+        
+        '# New Process resources
+        dim child as PROCESS_INFORMATION
+        dim context as STARTUPINFO
+        dim child_sa as SECURITY_ATTRIBUTES = type(sizeof(SECURITY_ATTRIBUTES), NULL, TRUE)
+        dim wait_code as DWORD
+        
+        '# StdIn, StdOut, StdErr Read and Write Pipes.
+        dim as HANDLE StdInRd, StdOutRd, StdErrRd
+        dim as HANDLE StdInWr, StdOutWr, StdErrWr
+        
+        debug("Spawn() init")
+        
+        '# to ensure everything will work, we must allocate a console
+        '# using AllocConsole, even if it fails.
+        '# This solve the problems when running as service.
+        if (AllocConsole() = 0) then
+            debug("Success in AllocConsole()")
+        else
+            debug("AllocConsole failed, maybe already allocated, safely to discart.")
+        end if
+        
+        '# presume all worked
+        success = TRUE
+        
+        '# Create the pipes
+        '# StdIn
+        if (CreatePipe( @StdInRd, @StdInWr, @child_sa, 0 ) = 0) then
+            debug("Error creating StdIn pipes.")
+            success = FALSE
+        end if
+        
+        '# StdOut
+        if (CreatePipe( @StdOutRd, @StdOutWr, @child_sa, 0 ) = 0) then
+            debug("Error creating StdOut pipes.")
+            success = FALSE
+        end if
+        
+        '# StdErr
+        if (CreatePipe( @StdErrRd, @StdErrWr, @child_sa, 0 ) = 0) then
+            debug("Error creating StdErr pipes.")
+            success = FALSE
+        end if
+        
+        '# Ensure the handles to the pipe are not inherited.
+        if (SetHandleInformation( StdInWr, HANDLE_FLAG_INHERIT, 0) = 0) then
+            debug("Error disabling StdInWr handle.")
+            success = FALSE
+        end if
+        
+        if (SetHandleInformation( StdOutRd, HANDLE_FLAG_INHERIT, 0) = 0) then
+            debug("Error disabling StdOutRd handle.")
+            success = FALSE
+        end if
+        
+        if (SetHandleInformation( StdErrRd, HANDLE_FLAG_INHERIT, 0) = 0) then
+            debug("Error disabling StdErrRd handle.")
+            success = FALSE
+        end if
+        
+        '# without the pipes, we shouldn't continue!
+        if (success = TRUE) then
+            '# Set the Std* handles ;-)
+            with context
+                .cb = sizeof( context )
+                .hStdError = StdErrWr
+                .hStdOutput = StdOutWr
+                .hStdInput = StdInRd
+                .dwFlags = STARTF_USESTDHANDLES
+            end with
+            
+            '# now creates the process
+            debug("Creating child process with cmdline: " + cmdline)
+            if (CreateProcess(NULL, _
+                                strptr(cmdline), _
+                                NULL, _
+                                NULL, _
+                                TRUE, _
+                                0, _
+                                NULL, _
+                                NULL, _
+                                @context, _
+                                @child) = 0) then
+                
+                debug("Error creating child process, error #" + str(GetLastError()))
+                result = 0
+            else
+                '# close the Std* handles
+                debug("Closing handles.")
+                CloseHandle(StdInRd)
+                CloseHandle(StdInWr)
+                CloseHandle(StdOutRd)
+                CloseHandle(StdOutWr)
+                CloseHandle(StdErrRd)
+                CloseHandle(StdErrWr)
+                
+                '# close children main Thread handle
+                if (CloseHandle(child.hThread) = 0) then
+                    debug("Problem closing children main thread.")
+                end if
+                
+                '# now wait 2 seconds if the children process unexpectly quits
+                wait_code = WaitForSingleObject(child.hProcess, 2000)
+                debug("wait_code: " + str(wait_code))
+                if (wait_code = WAIT_TIMEOUT) then
+                    '# the process is still running, we are good
+                    '# save a reference to the pid
+                    result = child.dwProcessId
+                    debug("New children PID: " + str(result))
+                    
+                    '# now close the handle
+                    CloseHandle(child.hProcess)
+                else
+                    '# the process failed or terminated earlier
+                    debug("failed, the process terminate earlier.")
+                    result = 0
+                end if '# (wait_code = WAIT_OBJECT_0)
+            end if '# (CreateProcess() = 0)
+        else
+            debug("problem preparing environment for child process, no success")
+            result = 0
+        end if '# (success = TRUE)
+        
+        debug("Spawn() done")
+        return result
+    end function
+    
+    
+    '# Terminate(PID) will hook the special console handler (_child_console_handler)
+    '# and try sending CTRL_C_EVENT, CTRL_BREAK_EVENT and TerminateProcess
+    '# in case of the first two fails.
+    function Terminate(pid as uinteger) as BOOL
+        dim result as BOOL
+        dim success as BOOL
+        dim exit_code as DWORD
+        dim wait_code as DWORD
+        
+        '# process resources
+        dim child as HANDLE
+        
+        debug("Terminate() init")
+        
+        '# is pid valid?
+        if (pid > 0) then
+            '# hook our custom console handler
+            debug("hooking console handler")
+            if (SetConsoleCtrlHandler(@_child_console_handler, TRUE) = 0) then
+                debug("error hooking our custom error handler")
+            end if
+            
+            '# get a handle to Process
+            debug("OpenProcess() with Terminate and Query flags")
+            child = OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_TERMINATE or SYNCHRONIZE, FALSE, pid)
+            if not (child = NULL) then
+                '# process is valid, perform actions
+                success = FALSE
+                
+                '# send CTRL_C_EVENT and wait for result
+                debug("sending Ctrl-C to child pid " + str(pid))
+                if not (GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) = 0) then
+                    '# it worked, wait 5 seconds terminates.
+                    debug("send worked, waiting 5 seconds to terminate...")
+                    wait_code = WaitForSingleObject(child, 5000)
+                    debug("wait_code: " + str(wait_code))
+                    if not (wait_code = WAIT_TIMEOUT) then
+                        debug("child process terminated properly.")
+                        success = TRUE
+                    end if
+                else
+                    debug("cannot generate event, error " + str(GetLastError()))
+                    success = FALSE
+                end if
+                
+                '# Ctrl-C didn't work, try Ctrl-Break
+                if (success = FALSE) then
+                    '# send CTRL_BREAK_EVENT and wait for result
+                    debug("sending Ctrl-Break to child pid " + str(pid))
+                    if not (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0) = 0) then
+                        '# it worked, wait 5 seconds terminates.
+                        debug("send worked, waiting 5 seconds to terminate...")
+                        wait_code = WaitForSingleObject(child, 5000)
+                        debug("wait_code: " + str(wait_code))
+                        if not (wait_code = WAIT_TIMEOUT) then
+                            debug("child process terminated properly.")
+                            success = TRUE
+                        end if
+                    else
+                        debug("cannot generate event, error " + str(GetLastError()))
+                        success = FALSE
+                    end if
+                end if
+                
+                '# still no luck? we should do a hard kill then
+                if (success = FALSE) then
+                    debug("doing kill using TerminateProcess")
+                    if (TerminateProcess(child, 3) = 0) then
+                        debug("TerminateProcess failed, error " + str(GetLastError()))
+                    else
+                        success = TRUE
+                    end if
+                end if
+                
+                '# now get process exit code
+                if not (GetExitCodeProcess(child, @exit_code) = 0) then
+                    debug("getting child process exit code: " + str(exit_code))
+                    if (exit_code = 0) then
+                        debug("process terminated ok.")
+                        result = TRUE
+                    elseif (exit_code = STILL_ACTIVE) then
+                        debug("process still active")
+                        result = FALSE
+                    else
+                        debug("process terminated with exit_code: " + str(exit_code))
+                        result = TRUE
+                    end if
+                else
+                    debug("error getting child process exit code, value: " + str(exit_code) + ", error " + str(GetLastError()))
+                    result = FALSE
+                end if
+            else
+                '# invalid process handler
+                result = FALSE
+            end if
+            
+            '# remove hooks
+            SetConsoleCtrlhandler(@_child_console_handler, FALSE)
+            
+            '# clean up all open handles
+            CloseHandle(child)
+        end if '# (pid > 0)
+        
+        return result
+    end function
+    
+    
+    '# Special hook used to avoid the process calling Terminate()
+    '# respond to CTRL_*_EVENTS when terminating child process
+    private function _child_console_handler(byval dwCtrlType as DWORD) as BOOL
+        dim result as BOOL
+        
+        debug("_child_console_handler, dwCtrlType: " + str(dwCtrlType))
+        if (dwCtrlType = CTRL_C_EVENT) then
+            result = TRUE
+        elseif (dwCtrlType = CTRL_BREAK_EVENT) then
+            result = TRUE
+        end if
+        
+        return result
+    end function
+end namespace '# fb.process
+end namespace '# fb
diff --git a/projects/mongrel_service/native/process.bi b/projects/mongrel_service/native/process.bi
new file mode 100644
index 0000000..18b931e
--- /dev/null
+++ b/projects/mongrel_service/native/process.bi
@@ -0,0 +1,48 @@
+'##################################################################
+'#
+'# mongrel_service: Win32 native implementation for mongrel
+'#                  (using ServiceFB and FreeBASIC)
+'#
+'# Copyright (c) 2006 Multimedia systems
+'# (c) and code by Luis Lavena
+'# Portions (c) Louis Thomas
+'#
+'#  mongrel_service (native) and mongrel_service gem_pluing are licensed
+'#  in the same terms as mongrel, please review the mongrel license at
+'#  http://mongrel.rubyforge.org/license.html
+'#  
+'##################################################################
+
+'##################################################################
+'# Requirements:
+'# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006).
+'#
+'##################################################################
+
+#ifndef __Process_bi__
+#define __Process_bi__
+
+#include once "windows.bi"
+
+namespace fb
+namespace process
+    '# fb.process functions that allow creation/graceful termination
+    '# of child process.
+    
+    '# Spawn(cmdline) will try to create a new process, monitor
+    '# if it launched successfuly (5 seconds) and then return the
+    '# new children PID (Process IDentification) or 0 in case of problems
+    declare function Spawn(byref as string) as uinteger
+    
+    '# Terminate(PID) will hook the special console handler (_child_console_handler)
+    '# and try sending CTRL_C_EVENT, CTRL_BREAK_EVENT and TerminateProcess
+    '# in case of the first two fails.
+    declare function Terminate(byval as uinteger) as BOOL
+    
+    '# Special hook used to avoid the process calling Terminate()
+    '# respond to CTRL_*_EVENTS when terminating child process
+    declare function _child_console_handler(byval as DWORD) as BOOL
+end namespace '# fb.process
+end namespace '# fb
+
+#endif '# __Process_bi__
diff --git a/projects/mongrel_service/tools/freebasic.rb b/projects/mongrel_service/tools/freebasic.rb
new file mode 100644
index 0000000..3df09e7
--- /dev/null
+++ b/projects/mongrel_service/tools/freebasic.rb
@@ -0,0 +1,336 @@
+#--
+# FreeBASIC project builder
+# inspired by Asset Compiler by Jeremy Voorhis
+# coded by Luis Lavena
+#--
+
+# FreeBASIC Project library for compilation.
+# For example:
+#
+# namespace :projects do
+#   ProjectTask.new( :my_fb_project ) do |p|
+#     p.type = :exe (executable), :lib (static), :dylib (dynamic, dll)
+#     p.build_path = File.join ROOT, 'bin/'
+#     p.version = '1.0.3'
+#
+#     p.main_file = File.join ROOT, 'src/main.bas'
+#     p.src_files = ['src/module1.bas', 'src/module2.bas']
+#     p.libraries = ['user32', 'lib/mylib']
+#   end
+# end
+#
+# This example defines the following tasks:
+#
+#   rake projects:build                 # Build all projects
+#   rake projects:clobber               # Clobber all projects
+#   rake projects:my_fb_project:build   # Build the my_fb_project files
+#   rake projects:my_fb_project:clobber # Remove the my_fb_project files
+#   rake projects:my_fb_project:rebuild # Force a rebuild of the my_fb_project files
+#   rake projects:rebuild               # Rebuild all projects
+
+require 'rake/tasklib'
+require 'pp'
+
+module FreeBASIC
+  # this help me reduce the attempts to remove already removed files.
+  # works with src_files
+  CLOBBER = Rake::FileList.new
+  ON_WINDOWS = (RUBY_PLATFORM =~ /mswin|cygwin|bccwin/)
+  
+  class ProjectTask
+    attr_accessor :name
+    attr_accessor :output_name
+    attr_accessor :type
+    attr_accessor :build_path
+    attr_accessor :defines
+    attr_accessor :main_file
+    attr_accessor :sources
+    attr_accessor :libraries
+    attr_accessor :search_path
+    attr_accessor :libraries_path
+    
+    def initialize(name, &block)
+      @name = name
+      @build_path = '.'
+      @defines = []
+      @sources = Rake::FileList.new
+      @libraries = []
+      @search_path = []
+      @libraries_path = []
+      
+      instance_eval &block
+      
+      do_cleanup
+      
+      namespace @name do
+        define_clobber_task
+        define_rebuild_task
+        define_build_directory_task
+        define_build_task
+      end
+      add_dependencies_to_main_task
+    end
+    
+    public
+      # using this will set your project type to :executable
+      # and assign exe_name as the output_name for the project
+      # it dynamically will assign extension when running on windows
+      def executable(exe_name)
+        @type = :executable
+        @output_name = "#{exe_name}.#{(ON_WINDOWS ? "exe" : "")}"
+        @real_file_name = @output_name
+      end
+      
+      # lib will set type to :lib and assign 'lib#{lib_name}.a'
+      # as output_name for the project
+      def lib(lib_name)
+        @type = :lib
+        @output_name = lib_name
+        @real_file_name = "lib#{lib_name}.a"
+      end
+      
+      # dynlib will set type to :dynlib and assign '#{dll_name}.dll|so'
+      # as output_name for this project
+      def dylib(dll_name)
+        @type = :dylib
+        @output_name = "#{dll_name}.#{(ON_WINDOWS ? "dll" : "so")}"
+        @real_file_name = @output_name
+        @complement_file = "lib#{@output_name}.a"
+      end
+      
+      # this set where the final, compiled executable should be linked
+      # uses @build_path as variable
+      def build_to(path)
+        @build_path = path
+      end
+      
+      # define allow you set compiler time options
+      # collect them into @defines and use later
+      # to call source file compilation process (it wil require cleanup)
+      def define(*defines)
+        @defines << defines
+      end
+      
+      # main set @main_file to be the main module used during the linking
+      # process. also, this module requires special -m flag during his
+      # own compile process
+      # question: in case @no main_file was set, will use the first 'source'
+      # file defined as main? or it should raise a error?
+      def main(main_file)
+        @main_file = main_file
+      end
+      
+      # used to collect sources files for compilation
+      # it will take .bas, .rc, .res as sources
+      # before compilation we should clean @sources!
+      def source(*sources)
+        @sources.include sources
+      end
+    
+      # this is similar to sources, instead library linking is used
+      # also will require cleanup services ;-)
+      def library(*libraries)
+        @libraries << libraries
+      end
+  
+      # use this to set the include path when looking for, ehem, includes
+      # (equals -i fbc compiler param)
+      def search_path(*search_path)
+        @search_path << search_path
+      end
+      
+      # use this to tell the compiler where to look for libraries
+      # (equals -p fbc compiler param)
+      def lib_path(*libraries_path)
+        @libraries_path << libraries_path
+      end
+      
+    protected
+      # this method will fix nested libraries and defines
+      # also, if main_file is missing (or wasn't set) will shift the first one
+      # as main
+      def do_cleanup
+        # remove duplicated definitions, flatten
+        @defines.flatten!
+        @defines.uniq! if @defines.length > 1
+
+        # set main_file
+        if @type == :executable
+          @main_file = @sources.shift unless defined?(@main_file)
+        end
+        
+        # empty path? must be corrected
+        @build_path = '.' if @build_path == ''
+        
+        # remove duplicates from sources
+        @sources.uniq! if @sources.length > 1
+        
+        # now the libraries
+        @libraries.flatten!
+        @libraries.uniq! if @libraries.length > 1
+        
+        # search path
+        @search_path.flatten!
+        @search_path.uniq! if @search_path.length > 1
+        
+        # libraries path
+        @libraries_path.flatten!
+        @libraries_path.uniq! if @libraries_path.length > 1
+        
+        # if no target was set, default to executable
+        unless defined?(@output_name)
+          executable(@name)
+        end
+      end
+      
+      # return the compiled name version of the passed source file (src)
+      # compiled_form("test.bas") => "test.o"
+      def compiled_form(src)
+        src.ext({ ".bas" => "o", ".rc" => "obj" }[File.extname(src)])
+      end
+      
+      def compiled_project_file
+        File.join @build_path, @real_file_name
+      end
+      
+      def fbc_compile(source, target, main = nil)
+        cmdline = []
+        cmdline << "fbc"
+        cmdline << "-c #{source}"
+        cmdline << "-o #{target}"
+        cmdline << "-m #{main}" unless main.nil?
+        cmdline << @defines.collect { |defname| "-d #{defname}" }
+        cmdline << @search_path.collect { |path| "-i #{path}" }
+        cmdline.flatten.join(' ')
+      end
+      
+      def fbc_link(target, files, extra_files = [])
+        cmdline = []
+        cmdline << "fbc"
+        cmdline << "-#{@type.to_s}" unless @type == :executable
+        cmdline << "-x #{target}"
+        cmdline << files << extra_files
+        cmdline << @defines.collect { |defname| "-d #{defname}" }
+        unless @type == :lib
+          cmdline << @libraries_path.collect { |path| "-p #{path}" }
+          cmdline << @libraries.collect { |libname| "-l #{libname}" }
+        end
+        cmdline.flatten.join(' ')
+      end
+      
+      def define_clobber_task
+        desc "Remove all compiled files for #{@name}"
+        task :clobber do
+          # remove compiled and linked file
+          rm compiled_project_file rescue nil #unless @type == :lib
+          rm File.join(@build_path, @complement_file) rescue nil if @type == :dylib
+          
+          # remove main file
+          rm compiled_form(@main_file) rescue nil
+          
+          # now the sources files
+          # avoid attempt to remove the file two times (this is a bug in Rake)
+          @sources.each do |src|
+            # exclude compiled source files (c obj).
+            unless src =~ /o$/
+              target = compiled_form(src)
+              unless CLOBBER.include?(target)
+                CLOBBER.include(target)
+                rm target rescue nil
+              end
+            end
+          end
+        end
+      end
+      
+      def define_rebuild_task
+        desc "Force a rebuild of files for #{@name}"
+        task :rebuild => [:clobber, :build]
+      end
+      
+      def define_build_directory_task
+        directory @build_path
+        task :build => @build_path
+      end
+      
+      def define_build_task
+        desc "Build project #{@name}"
+        task :build
+        
+        # empty file task
+        file compiled_project_file
+        
+        # compile main_file
+        # use as pre-requisite the source filename
+        if @type == :executable
+          file compiled_form(@main_file) => @main_file do |t|
+            # remove the path and the extension
+            main_module = File.basename(t.name).ext
+            sh fbc_compile(@main_file, t.name, main_module)
+          end
+        
+          # add dependency
+          file compiled_project_file => compiled_form(@main_file)
+        end
+        
+        # gather files that are passed "as-is" to the compiler
+        unprocessed_files = @sources.select { |rcfile| rcfile =~ /(res|rc|o|obj)$/ }
+        
+        @sources.each do |src|
+          # is a unprocessed file?
+          unless unprocessed_files.include?(src)
+            target = compiled_form(src)
+            
+            # is already in our list of tasks?
+            if not Rake::Task.task_defined?(target)
+              # if not, compile
+              
+              file target => src do
+                sh fbc_compile(src, target)
+              end
+            end
+          
+            # include dependency
+            file compiled_project_file => target
+          end
+        end
+        
+        # now the linking process
+        file compiled_project_file do |t|
+          target = File.join(@build_path, @output_name)
+          sh fbc_link(target, t.prerequisites, unprocessed_files)
+        end
+        
+        # add the dependency
+        task :build => compiled_project_file
+      end
+
+      # Adds dependencies in the parent namespace
+      def add_dependencies_to_main_task
+        desc 'Build all projects' unless task( :build ).comment
+        task :build => "#{@name}:build"
+        
+        desc 'Clobber all projects' unless task( :clobber ).comment
+        task :clobber => "#{@name}:clobber"
+        
+        desc 'Rebuild all projects' unless task( :rebuild ).comment
+        task :rebuild => ["#{@name}:clobber", "#{@name}:build"]
+      end
+  end
+end
+  
+# helper method to define a FreeBASIC::ProjectTask in the current namespace
+def project_task name, &block
+  FreeBASIC::ProjectTask.new name, &block
+end
+
+def include_projects_of name
+  desc 'Build all projects' unless task( :build ).comment
+  task :build => "#{name}:build"
+  
+  desc 'Clobber all projects' unless task( :clobber ).comment
+  task :clobber => "#{name}:clobber"
+  
+  desc 'Rebuild all projects' unless task( :rebuild ).comment
+  task :rebuild => "#{name}:rebuild"
+end