diff options
author | zedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9> | 2006-11-25 01:10:57 +0000 |
---|---|---|
committer | zedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9> | 2006-11-25 01:10:57 +0000 |
commit | 7f17c43dab3a257d040ab66617b98c7f1cafc2b8 (patch) | |
tree | 9f4de223c0acbbfc2a4a6ac08260f3e552985e1c | |
parent | 7bbe221d778d8b46af3c3ea3135a72f0d4d7362d (diff) | |
download | unicorn-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/CHANGELOG | 5 | ||||
-rw-r--r-- | projects/mongrel_service/README | 4 | ||||
-rw-r--r-- | projects/mongrel_service/Rakefile | 71 | ||||
-rw-r--r-- | projects/mongrel_service/TODO | 7 | ||||
-rw-r--r-- | projects/mongrel_service/bin/mongrel_service | 156 | ||||
-rw-r--r-- | projects/mongrel_service/lib/mongrel_service/init.rb | 142 | ||||
-rw-r--r-- | projects/mongrel_service/native/_debug.bi | 60 | ||||
-rw-r--r-- | projects/mongrel_service/native/mongrel_service.bas | 149 | ||||
-rw-r--r-- | projects/mongrel_service/native/mongrel_service.bi | 50 | ||||
-rw-r--r-- | projects/mongrel_service/native/process.bas | 284 | ||||
-rw-r--r-- | projects/mongrel_service/native/process.bi | 48 | ||||
-rw-r--r-- | projects/mongrel_service/tools/freebasic.rb | 336 |
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
|