diff --git a/CHANGES b/CHANGES index bee90f48e..53c320065 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ * Added existing and existing! methods to FileList * FileLists now claim to be Arrays (via is_a?) to get better support from the FileUtil module. +* Added init and top_level for custom rake applications. == Version 0.7.2 diff --git a/doc/release_notes/rake-0.7.3.rdoc b/doc/release_notes/rake-0.7.3.rdoc new file mode 100755 index 000000000..39e91bb98 --- /dev/null +++ b/doc/release_notes/rake-0.7.3.rdoc @@ -0,0 +1,47 @@ += Rake 0.7.3 Released + +Rake version 0.7.3 is a minor release that includes some refactoring to better +support custom Rake applications. + +== Changes + +=== New Features in Version 0.7.3 + +* Added the +init+ and +top_level+ methods to make the creation of custom Rake applications a bit easier. E.g. + + gem 'rake', ">= 0.7.3" + require 'rake' + + Rake.application.init('myrake') + + task :default do + something_interesting + end + + Rake.application.top_level + +== What is Rake + +Rake is a build tool similar to the make program in many ways. But instead of +cryptic make recipes, Rake uses standard Ruby code to declare tasks and +dependencies. You have the full power of a modern scripting language built +right into your build tool. + +== Availability + +The easiest way to get and install rake is via RubyGems ... + + gem install rake (you may need root/admin privileges) + +Otherwise, you can get it from the more traditional places: + +Home Page:: http://rake.rubyforge.org/ +Download:: http://rubyforge.org/project/showfiles.php?group_id=50 + +== Thanks + +As usual, it was input from users that drove a alot of these changes. The +following people either contributed patches, made suggestions or made +otherwise helpful comments. Thanks to ... + +-- Jim Weirich diff --git a/lib/rake.rb b/lib/rake.rb index 0b39a270c..28dcbb333 100755 --- a/lib/rake.rb +++ b/lib/rake.rb @@ -1,35 +1,35 @@ #!/usr/bin/env ruby #-- + # Copyright (c) 2003, 2004, 2005, 2006 Jim Weirich # -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. #++ # # = Rake -- Ruby Make -# -# This is the main file for the Rake application. Normally it is -# referenced as a library via a require statement, but it can be -# distributed independently as an application. +# +# This is the main file for the Rake application. Normally it is referenced +# as a library via a require statement, but it can be distributed +# independently as an application. -RAKEVERSION = '0.7.2.1' +RAKEVERSION = '0.7.3' require 'rbconfig' require 'ftools' @@ -44,10 +44,10 @@ # class Module - # Check for an existing method in the current class before - # extending. IF the method already exists, then a warning is - # printed and the extension is not added. Otherwise the block is - # yielded and any definitions in the block will take effect. + # Check for an existing method in the current class before extending. IF + # the method already exists, then a warning is printed and the extension is + # not added. Otherwise the block is yielded and any definitions in the + # block will take effect. # # Usage: # @@ -74,10 +74,9 @@ def rake_extension(method) # class String rake_extension("ext") do - # Replace the file extension with +newext+. If there is no - # extenson on the string, append the new extension to the end. If - # the new extension is not given, or is the empty string, remove - # any existing extension. + # Replace the file extension with +newext+. If there is no extenson on + # the string, append the new extension to the end. If the new extension + # is not given, or is the empty string, remove any existing extension. # # +ext+ is a user added method for the String class. def ext(newext='') @@ -100,10 +99,9 @@ def pathmap_explode end protected :pathmap_explode - # Extract a partial path from the path. Include +n+ directories - # from the front end (left hand side) if +n+ is positive. Include - # |+n+| directories from the back end (right hand side) if +n+ is - # negative. + # Extract a partial path from the path. Include +n+ directories from the + # front end (left hand side) if +n+ is positive. Include |+n+| + # directories from the back end (right hand side) if +n+ is negative. def pathmap_partial(n) target = File.dirname(self) dirs = target.pathmap_explode @@ -122,8 +120,8 @@ def pathmap_partial(n) end protected :pathmap_partial - # Preform the pathmap replacement operations on the given path. - # The patterns take the form 'pat1,rep1;pat2,rep2...'. + # Preform the pathmap replacement operations on the given path. The + # patterns take the form 'pat1,rep1;pat2,rep2...'. def pathmap_replace(patterns, &block) result = self patterns.split(';').each do |pair| @@ -141,28 +139,26 @@ def pathmap_replace(patterns, &block) end protected :pathmap_replace - # Map the path according to the given specification. The - # specification controls the details of the mapping. The - # following special patterns are recognized: + # Map the path according to the given specification. The specification + # controls the details of the mapping. The following special patterns are + # recognized: # # * %p -- The complete path. - # * %f -- The base file name of the path, with its file - # extension, but without any directories. - # * %n -- The file name of the path without its file - # extension. + # * %f -- The base file name of the path, with its file extension, + # but without any directories. + # * %n -- The file name of the path without its file extension. # * %d -- The directory list of the path. - # * %x -- The file extension of the path. An empty string - # if there is no extension. + # * %x -- The file extension of the path. An empty string if there + # is no extension. # * %X -- Everything *but* the file extension. - # * %s -- The alternate file separater if defined, - # otherwise use the standard file separator. + # * %s -- The alternate file separater if defined, otherwise use + # the standard file separator. # * %% -- A percent sign. # - # The %d specifier can also have a numeric prefix (e.g. '%2d'). - # If the number is positive, only return (up to) +n+ directories - # in the path, starting from the left hand side. If +n+ is - # negative, return (up to) |+n+| directories from the right hand - # side of the path. + # The %d specifier can also have a numeric prefix (e.g. '%2d'). If the + # number is positive, only return (up to) +n+ directories in the path, + # starting from the left hand side. If +n+ is negative, return (up to) + # |+n+| directories from the right hand side of the path. # # Examples: # @@ -170,29 +166,28 @@ def pathmap_replace(patterns, &block) # 'a/b/c/d/file.txt'.pathmap("%-2d") => 'c/d' # # Also the %d, %p, $f, $n, %x, and %X operators can take a - # pattern/replacement argument to perform simple string - # substititions on a particular part of the path. The pattern and - # replacement are speparated by a comma and are enclosed by curly - # braces. The replacement spec comes after the % character but - # before the operator letter. (e.g. "%{old,new}d"). Muliple - # replacement specs should be separated by semi-colons - # (e.g. "%{old,new;src,bin}d"). + # pattern/replacement argument to perform simple string substititions on a + # particular part of the path. The pattern and replacement are speparated + # by a comma and are enclosed by curly braces. The replacement spec comes + # after the % character but before the operator letter. (e.g. + # "%{old,new}d"). Muliple replacement specs should be separated by + # semi-colons (e.g. "%{old,new;src,bin}d"). # - # Regular expressions may be used for the pattern, and back refs - # may be used in the replacement text. Curly braces, commas and - # semi-colons are excluded from both the pattern and replacement - # text (let's keep parsing reasonable). + # Regular expressions may be used for the pattern, and back refs may be + # used in the replacement text. Curly braces, commas and semi-colons are + # excluded from both the pattern and replacement text (let's keep parsing + # reasonable). # # For example: # # "src/org/onestepback/proj/A.java".pathmap("%{^src,bin}X.class") - # + # # returns: # # "bin/org/onestepback/proj/A.class" # - # If the replacement text is '*', then a block may be provided to - # perform some arbitrary calculation for the replacement. + # If the replacement text is '*', then a block may be provided to perform + # some arbitrary calculation for the replacement. # # For example: # @@ -247,10 +242,10 @@ def pathmap(spec=nil, &block) end end -###################################################################### +############################################################################## module Rake - # ------------------------------------------------------------------ + # -------------------------------------------------------------------------- # Rake module singleton methods. # class << self @@ -264,20 +259,19 @@ def application=(app) @application = app end - # Return the original directory where the Rake application was - # started. + # Return the original directory where the Rake application was started. def original_dir application.original_dir end end - #################################################################### + # ########################################################################## # Mixin for creating easily cloned objects. # module Cloneable - # Clone an object by making a new object and setting all the - # instance variables to the same values. + # Clone an object by making a new object and setting all the instance + # variables to the same values. def clone sibling = self.class.new instance_variables.each do |ivar| @@ -293,15 +287,14 @@ def clone module Rake - ###################################################################### - # A Task is the basic unit of work in a Rakefile. Tasks have - # associated actions (possibly more than one) and a list of - # prerequisites. When invoked, a task will first ensure that all of - # its prerequisites have an opportunity to run and then it will - # execute its own actions. + # ######################################################################### + # A Task is the basic unit of work in a Rakefile. Tasks have associated + # actions (possibly more than one) and a list of prerequisites. When + # invoked, a task will first ensure that all of its prerequisites have an + # opportunity to run and then it will execute its own actions. # - # Tasks are not usually created directly using the new method, but - # rather use the +file+ and +task+ convenience methods. + # Tasks are not usually created directly using the new method, but rather + # use the +file+ and +task+ convenience methods. # class Task # List of prerequisites for a task. @@ -332,8 +325,8 @@ def source @sources.first if defined?(@sources) end - # Create a task named +task_name+ with no actions or prerequisites.. - # use +enhance+ to add actions and prerequisites. + # Create a task named +task_name+ with no actions or prerequisites. Use + # +enhance+ to add actions and prerequisites. def initialize(task_name, app) @name = task_name.to_s @prerequisites = FileList[] @@ -404,8 +397,8 @@ def needed? true end - # Timestamp for this task. Basic tasks return the current time for - # their time stamp. Other tasks can be more sophisticated. + # Timestamp for this task. Basic tasks return the current time for their + # time stamp. Other tasks can be more sophisticated. def timestamp @prerequisites.collect { |p| application[p].timestamp }.max || Time.now end @@ -422,8 +415,8 @@ def add_comment(comment) @comment << comment end - # Return a string describing the internal state of a task. Useful - # for debugging. + # Return a string describing the internal state of a task. Useful for + # debugging. def investigation result = "------------------------------\n" result << "Investigating #{name}\n" @@ -447,9 +440,8 @@ def investigation # class << self - # Clear the task list. This cause rake to immediately forget all - # the tasks that have been assigned. (Normally used in the unit - # tests.) + # Clear the task list. This cause rake to immediately forget all the + # tasks that have been assigned. (Normally used in the unit tests.) def clear Rake.application.clear end @@ -460,9 +452,9 @@ def tasks end # Return a task with the given name. If the task is not currently - # known, try to synthesize one from the defined rules. If no - # rules are found, but an existing file matches the task name, - # assume it is a file task with no dependencies or actions. + # known, try to synthesize one from the defined rules. If no rules are + # found, but an existing file matches the task name, assume it is a file + # task with no dependencies or actions. def [](task_name) Rake.application[task_name] end @@ -472,9 +464,9 @@ def task_defined?(task_name) Rake.application.lookup(task_name) != nil end - # Define a task given +args+ and an option block. If a rule with - # the given name already exists, the prerequisites and actions are - # added to the existing task. Returns the defined task. + # Define a task given +args+ and an option block. If a rule with the + # given name already exists, the prerequisites and actions are added to + # the existing task. Returns the defined task. def define_task(args, &block) Rake.application.define_task(self, args, &block) end @@ -495,16 +487,16 @@ def scope_name(scope, task_name) end - ###################################################################### - # A FileTask is a task that includes time based dependencies. If - # any of a FileTask's prerequisites have a timestamp that is later - # than the file represented by this task, then the file must be - # rebuilt (using the supplied actions). + # ######################################################################### + # A FileTask is a task that includes time based dependencies. If any of a + # FileTask's prerequisites have a timestamp that is later than the file + # represented by this task, then the file must be rebuilt (using the + # supplied actions). # class FileTask < Task - # Is this file task needed? Yes if it doesn't exist, or if its time - # stamp is out of date. + # Is this file task needed? Yes if it doesn't exist, or if its time stamp + # is out of date. def needed? return true unless File.exist?(name) return true if out_of_date?(timestamp) @@ -522,8 +514,7 @@ def timestamp private - # Are there any prerequisites with a later time than the given - # time stamp? + # Are there any prerequisites with a later time than the given time stamp? def out_of_date?(stamp) @prerequisites.any? { |n| application[n].timestamp > stamp} end @@ -532,21 +523,20 @@ def out_of_date?(stamp) # Task class methods. # class << self - # Apply the scope to the task name according to the rules for - # this kind of task. File based tasks ignore the scope when - # creating the name. + # Apply the scope to the task name according to the rules for this kind + # of task. File based tasks ignore the scope when creating the name. def scope_name(scope, task_name) task_name end end end - ###################################################################### - # A FileCreationTask is a file task that when used as a dependency - # will be needed if and only if the file has not been created. Once - # created, it is not re-triggered if any of its dependencies are - # newer, nor does trigger any rebuilds of tasks that depend on it - # whenever it is updated. + # ######################################################################### + # A FileCreationTask is a file task that when used as a dependency will be + # needed if and only if the file has not been created. Once created, it is + # not re-triggered if any of its dependencies are newer, nor does trigger + # any rebuilds of tasks that depend on it whenever it is updated. + # class FileCreationTask < FileTask # Is this file task needed? Yes if it doesn't exist. def needed? @@ -560,9 +550,9 @@ def timestamp end end - #################################################################### - # Same as a regular task, but the immediate prerequisites are done - # in parallel using Ruby threads. + # ######################################################################### + # Same as a regular task, but the immediate prerequisites are done in + # parallel using Ruby threads. # class MultiTask < Task def invoke_prerequisites @@ -574,7 +564,7 @@ def invoke_prerequisites end end -###################################################################### +# ########################################################################### # Task Definition Functions ... # Declare a basic task. @@ -612,8 +602,7 @@ def file_create(args, &block) Rake::FileCreationTask.define_task(args, &block) end -# Declare a set of files tasks to create the given directories on -# demand. +# Declare a set of files tasks to create the given directories on demand. # # Example: # directory "testdata/doc" @@ -626,9 +615,9 @@ def directory(dir) end end -# Declare a task that performs its prerequisites in parallel. -# Multitasks does *not* guarantee that its prerequisites will execute -# in any given order (which is obvious when you think about it) +# Declare a task that performs its prerequisites in parallel. Multitasks does +# *not* guarantee that its prerequisites will execute in any given order +# (which is obvious when you think about it) # # Example: # multitask :deploy => [:deploy_gem, :deploy_rdoc] @@ -637,9 +626,9 @@ def multitask(args, &block) Rake::MultiTask.define_task(args, &block) end -# Create a new rake namespace and use it for evaluating the given -# block. Returns a NameSpace object that can be used to lookup tasks -# defined in the namespace. +# Create a new rake namespace and use it for evaluating the given block. +# Returns a NameSpace object that can be used to lookup tasks defined in the +# namespace. # # E.g. # @@ -675,11 +664,10 @@ def desc(comment) Rake.application.last_comment = comment end -# Import the partial Rakefiles +fn+. Imported files are loaded -# _after_ the current file is completely loaded. This allows the -# import statement to appear anywhere in the importing file, and yet -# allowing the imported files to depend on objects defined in the -# importing file. +# Import the partial Rakefiles +fn+. Imported files are loaded _after_ the +# current file is completely loaded. This allows the import statement to +# appear anywhere in the importing file, and yet allowing the imported files +# to depend on objects defined in the importing file. # # A common use of the import statement is to include files containing # dependency declarations. @@ -695,9 +683,9 @@ def import(*fns) end end -###################################################################### -# This a FileUtils extension that defines several additional commands -# to be added to the FileUtils utility functions. +# ########################################################################### +# This a FileUtils extension that defines several additional commands to be +# added to the FileUtils utility functions. # module FileUtils RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) @@ -705,9 +693,9 @@ module FileUtils OPT_TABLE['sh'] = %w(noop verbose) OPT_TABLE['ruby'] = %w(noop verbose) - # Run the system command +cmd+. If multiple arguments are given - # the command is not run with the shell (same semantics as - # Kernel::exec and Kernel::system). + # Run the system command +cmd+. If multiple arguments are given the command + # is not run with the shell (same semantics as Kernel::exec and + # Kernel::system). # # Example: # sh %{ls -ltr} @@ -755,8 +743,8 @@ def ruby(*args,&block) LN_SUPPORTED = [true] - # Attempt to do a normal file link, but fall back to a copy if the - # link fails. + # Attempt to do a normal file link, but fall back to a copy if the link + # fails. def safe_ln(*args) unless LN_SUPPORTED[0] cp(*args) @@ -783,9 +771,9 @@ def split_all(path) end end -###################################################################### -# RakeFileUtils provides a custom version of the FileUtils methods -# that respond to the verbose and nowrite commands. +# ########################################################################### +# RakeFileUtils provides a custom version of the FileUtils methods that +# respond to the verbose and nowrite commands. # module RakeFileUtils include FileUtils @@ -819,9 +807,8 @@ def #{name}( *args, &block ) EOS end - # Get/set the verbose flag controlling output from the FileUtils - # utilities. If verbose is true, then the utility method is echoed - # to standard output. + # Get/set the verbose flag controlling output from the FileUtils utilities. + # If verbose is true, then the utility method is echoed to standard output. # # Examples: # verbose # return the current value of the verbose flag @@ -841,9 +828,8 @@ def verbose(value=nil) RakeFileUtils.verbose_flag end - # Get/set the nowrite flag controlling output from the FileUtils - # utilities. If verbose is true, then the utility method is echoed - # to standard output. + # Get/set the nowrite flag controlling output from the FileUtils utilities. + # If verbose is true, then the utility method is echoed to standard output. # # Examples: # nowrite # return the current value of the nowrite flag @@ -863,8 +849,8 @@ def nowrite(value=nil) oldvalue end - # Use this function to prevent protentially destructive ruby code - # from running when the :nowrite flag is set. + # Use this function to prevent protentially destructive ruby code from + # running when the :nowrite flag is set. # # Example: # @@ -872,8 +858,8 @@ def nowrite(value=nil) # project.build # end # - # The following code will build the project under normal conditions. - # If the nowrite(true) flag is set, then the example will print: + # The following code will build the project under normal conditions. If the + # nowrite(true) flag is set, then the example will print: # DRYRUN: Building Project # instead of actually building the project. # @@ -902,9 +888,8 @@ def rake_output_message(message) end private :rake_output_message - # Check that the options do not contain options not listed in - # +optdecl+. An ArgumentError exception is thrown if non-declared - # options are found. + # Check that the options do not contain options not listed in +optdecl+. An + # ArgumentError exception is thrown if non-declared options are found. def rake_check_options(options, *optdecl) h = options.dup optdecl.each do |name| @@ -917,10 +902,10 @@ def rake_check_options(options, *optdecl) extend self end -###################################################################### -# Include the FileUtils file manipulation functions in the top level -# module, but mark them private so that they don't unintentionally -# define methods on other objects. +# ########################################################################### +# Include the FileUtils file manipulation functions in the top level module, +# but mark them private so that they don't unintentionally define methods on +# other objects. include RakeFileUtils private(*FileUtils.instance_methods(false)) @@ -944,20 +929,19 @@ def message end end - #################################################################### - # A FileList is essentially an array with a few helper methods - # defined to make file manipulation a bit easier. + # ######################################################################### + # A FileList is essentially an array with a few helper methods defined to + # make file manipulation a bit easier. # - # FileLists are lazy. When given a list of glob patterns for - # possible files to be included in the file list, instead of - # searching the file structures to find the files, a FileList holds - # the pattern for latter use. + # FileLists are lazy. When given a list of glob patterns for possible files + # to be included in the file list, instead of searching the file structures + # to find the files, a FileList holds the pattern for latter use. # # This allows us to define a number of FileList to match any number of - # files, but only search out the actual files when then FileList - # itself is actually used. The key is that the first time an - # element of the FileList/Array is requested, the pending patterns - # are resolved into a real list of file names. + # files, but only search out the actual files when then FileList itself is + # actually used. The key is that the first time an element of the + # FileList/Array is requested, the pending patterns are resolved into a real + # list of file names. # class FileList @@ -965,22 +949,19 @@ class FileList # == Method Delegation # - # The lazy evaluation magic of FileLists happens by implementing - # all the array specific methods to call +resolve+ before - # delegating the heavy lifting to an embedded array object - # (@items). + # The lazy evaluation magic of FileLists happens by implementing all the + # array specific methods to call +resolve+ before delegating the heavy + # lifting to an embedded array object (@items). # - # In addition, there are two kinds of delegation calls. The - # regular kind delegates to the @items array and returns the - # result directly. Well, almost directly. It checks if the - # returned value is the @items object itself, and if so will - # return the FileList object instead. + # In addition, there are two kinds of delegation calls. The regular kind + # delegates to the @items array and returns the result directly. Well, + # almost directly. It checks if the returned value is the @items object + # itself, and if so will return the FileList object instead. # - # The second kind of delegation call is used in methods that - # normally return a new Array object. We want to capture the - # return value of these methods and wrap them in a new FileList - # object. We enumerate these methods in the +SPECIAL_RETURN+ list - # below. + # The second kind of delegation call is used in methods that normally + # return a new Array object. We want to capture the return value of these + # methods and wrap them in a new FileList object. We enumerate these + # methods in the +SPECIAL_RETURN+ list below. # List of array methods (that are not in +Object+) that need to be # delegated. @@ -989,12 +970,12 @@ class FileList # List of additional methods that must be delegated. MUST_DEFINE = %w[to_a inspect] - # List of methods that should not be delegated here (we define - # special versions of them explicitly below). + # List of methods that should not be delegated here (we define special + # versions of them explicitly below). MUST_NOT_DEFINE = %w[to_a to_ary partition *] - # List of delegated methods that return new array values which - # need wrapping. + # List of delegated methods that return new array values which need + # wrapping. SPECIAL_RETURN = %w[ map collect sort sort_by select find_all reject grep compact flatten uniq values_at @@ -1026,9 +1007,9 @@ def #{sym}(*args, &block) end end - # Create a file list from the globbable patterns given. If you - # wish to perform multiple includes or excludes at object build - # time, use the "yield self" pattern. + # Create a file list from the globbable patterns given. If you wish to + # perform multiple includes or excludes at object build time, use the + # "yield self" pattern. # # Example: # file_list = FileList.new('lib/**/*.rb', 'test/test*.rb') @@ -1048,8 +1029,8 @@ def initialize(*patterns) yield self if block_given? end - # Add file names defined by glob patterns to the file list. If an - # array is given, add each element of the array. + # Add file names defined by glob patterns to the file list. If an array + # is given, add each element of the array. # # Example: # file_list.include("*.java", "*.cfg") @@ -1069,16 +1050,15 @@ def include(*filenames) end alias :add :include - # Register a list of file name patterns that should be excluded - # from the list. Patterns may be regular expressions, glob - # patterns or regular strings. In addition, a block given to - # exclude will remove entries that return true when given to the - # block. + # Register a list of file name patterns that should be excluded from the + # list. Patterns may be regular expressions, glob patterns or regular + # strings. In addition, a block given to exclude will remove entries that + # return true when given to the block. # - # Note that glob patterns are expanded against the file system. - # If a file is explicitly added to a file list, but does not exist - # in the file system, then an glob pattern in the exclude list - # will not exclude the file. + # Note that glob patterns are expanded against the file system. If a file + # is explicitly added to a file list, but does not exist in the file + # system, then an glob pattern in the exclude list will not exclude the + # file. # # Examples: # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c'] @@ -1191,8 +1171,8 @@ def resolve_exclude end private :resolve_exclude - # Return a new FileList with the results of running +sub+ against - # each element of the oringal list. + # Return a new FileList with the results of running +sub+ against each + # element of the oringal list. # # Example: # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o'] @@ -1201,8 +1181,8 @@ def sub(pat, rep) inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) } end - # Return a new FileList with the results of running +gsub+ against - # each element of the original list. + # Return a new FileList with the results of running +gsub+ against each + # element of the original list. # # Example: # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\") @@ -1224,15 +1204,15 @@ def gsub!(pat, rep) self end - # Apply the pathmap spec to each of the included file names, - # returning a new file list with the modified paths. (See - # String#pathmap for details.) + # Apply the pathmap spec to each of the included file names, returning a + # new file list with the modified paths. (See String#pathmap for + # details.) def pathmap(spec=nil) collect { |fn| fn.pathmap(spec) } end - # Return a new array with String#ext method applied to - # each member of the array. + # Return a new array with String#ext method applied to each + # member of the array. # # This method is a shortcut for: # @@ -1244,11 +1224,11 @@ def ext(newext='') end - # Grep each of the files in the filelist using the given pattern. - # If a block is given, call the block on each matching line, - # passing the file name, line number, and the matching line of - # text. If no block is given, a standard emac style - # file:linenumber:line message will be printed to standard out. + # Grep each of the files in the filelist using the given pattern. If a + # block is given, call the block on each matching line, passing the file + # name, line number, and the matching line of text. If no block is given, + # a standard emac style file:linenumber:line message will be printed to + # standard out. def egrep(pattern) each do |fn| open(fn) do |inf| @@ -1281,8 +1261,8 @@ def existing! self end - # FileList version of partition. Needed because the nested arrays - # should be FileLists in this version. + # FileList version of partition. Needed because the nested arrays should + # be FileLists in this version. def partition(&block) # :nodoc: resolve result = @items.partition(&block) @@ -1336,17 +1316,17 @@ def [](*args) new(*args) end - # Set the ignore patterns back to the default value. The - # default patterns will ignore files + # Set the ignore patterns back to the default value. The default + # patterns will ignore files # * containing "CVS" in the file path # * containing ".svn" in the file path # * ending with ".bak" # * ending with "~" # * named "core" # - # Note that file names beginning with "." are automatically - # ignored by Ruby's glob patterns and are not specifically - # listed in the ignore patterns. + # Note that file names beginning with "." are automatically ignored by + # Ruby's glob patterns and are not specifically listed in the ignore + # patterns. def select_default_ignore_patterns @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup end @@ -1377,7 +1357,7 @@ def each_dir_parent(dir) # Alias FileList to be available at the top level. FileList = Rake::FileList -###################################################################### +# ########################################################################### module Rake # Default Rakefile loader used by +import+. @@ -1387,8 +1367,7 @@ def load(fn) end end - # EarlyTime is a fake timestamp that occurs _before_ any other time - # value. + # EarlyTime is a fake timestamp that occurs _before_ any other time value. class EarlyTime include Comparable include Singleton @@ -1405,7 +1384,7 @@ def to_s EARLY = EarlyTime.instance end -###################################################################### +# ########################################################################### # Extensions to time to allow comparisons with an early time class. # class Time @@ -1651,12 +1630,21 @@ def make_sources(task_name, extensions) class Application include TaskManager + # The name of the application (typically 'rake') + attr_reader :name + # The original directory where rake was invoked. - attr_reader :original_dir, :rakefile + attr_reader :original_dir + + # Name of the actual rakefile used. + attr_reader :rakefile + + # List of the top level task names (task names from the command line). + attr_reader :top_level_tasks DEFAULT_RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb'].freeze - OPTIONS = [ + OPTIONS = [ # :nodoc: ['--dry-run', '-n', GetoptLong::NO_ARGUMENT, "Do a dry run without executing actions."], ['--help', '-H', GetoptLong::NO_ARGUMENT, @@ -1691,9 +1679,10 @@ class Application "Put Task and FileTask in the top level namespace"], ] - # Create a Rake::Application object. + # Initialize a Rake::Application object. def initialize super + @name = 'rake' @rakefiles = DEFAULT_RAKEFILES.dup @rakefile = nil @pending_imports = [] @@ -1701,15 +1690,92 @@ def initialize @loaders = {} @default_loader = Rake::DefaultLoader.new @original_dir = Dir.pwd + @top_level_tasks = [] add_loader('rf', DefaultLoader.new) add_loader('rake', DefaultLoader.new) end + + # Run the Rake application. The run method performs the following three steps: + # + # * Initialize the command line options (+init+). + # * Define the tasks (+load_rakefile+). + # * Run the top level tasks (+run_tasks+). + # + # If you wish to build a custom rake command, you should call +init+ on your + # application. The define any tasks. Finally, call +top_level+ to run your top + # level tasks. + def run + standard_exception_handling do + init + load_rakefile + top_level + end + end + + # Initialize the command line parameters and app name. + def init(app_name='rake') + standard_exception_handling do + @name = app_name + handle_options + collect_tasks + end + end + + # Find the rakefile and then load it and any pending imports. + def load_rakefile + standard_exception_handling do + raw_load_rakefile + end + end + + # Run the top level tasks of a Rake application. + def top_level + standard_exception_handling do + if options.show_tasks + display_tasks_and_comments + elsif options.show_prereqs + display_prerequisites + else + top_level_tasks.each { |task_name| self[task_name].invoke } + end + end + end + + # Add a loader to handle imported files ending in the extension + # +ext+. + def add_loader(ext, loader) + ext = ".#{ext}" unless ext =~ /^\./ + @loaders[ext] = loader + end # Application options from the command line def options @options ||= OpenStruct.new end + private + + # Provide standard execption handling for the given block. + def standard_exception_handling + begin + yield + rescue SystemExit, GetoptLong::InvalidOption => ex + # Exit silently + exit(1) + rescue Exception => ex + # Exit with error message + $stderr.puts "rake aborted!" + $stderr.puts ex.message + if options.trace + $stderr.puts ex.backtrace.join("\n") + else + $stderr.puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || "" + $stderr.puts "(See full trace by running task with --trace)" + end + exit(1) + end + end + # True if one of the files in RAKEFILES is in the current directory. # If a match is found, it is copied into @rakefile. def have_rakefile @@ -1751,7 +1817,7 @@ def display_tasks_and_comments } width = displayable_tasks.collect { |t| t.name.length }.max displayable_tasks.each do |t| - printf "rake %-#{width}s # %s\n", t.name, t.comment + printf "#{name} %-#{width}s # %s\n", t.name, t.comment end end @@ -1860,8 +1926,7 @@ def rake_require(file_name, paths=$LOAD_PATH, loaded=$") fail LoadError, "Can't find #{file_name}" end - # Find the rakefile and then load it and any pending imports. - def load_rakefile + def raw_load_rakefile # :nodoc: here = Dir.pwd while ! have_rakefile Dir.chdir("..") @@ -1883,16 +1948,15 @@ def load_rakefile # given, return a list containing only the default task. # Environmental assignments are processed at this time as well. def collect_tasks - tasks = [] + @top_level_tasks = [] ARGV.each do |arg| if arg =~ /^(\w+)=(.*)$/ ENV[$1] = $2 else - tasks << arg + @top_level_tasks << arg end end - tasks.push("default") if tasks.size == 0 - tasks + @top_level_tasks.push("default") if @top_level_tasks.size == 0 end # Add a file to the list of files to be imported. @@ -1914,13 +1978,6 @@ def load_imports end end - # Add a loader to handle imported files ending in the extension - # +ext+. - def add_loader(ext, loader) - ext = ".#{ext}" unless ext =~ /^\./ - @loaders[ext] = loader - end - # Warn about deprecated use of top level constant names. def const_warning(const_name) @const_warning ||= false @@ -1940,36 +1997,6 @@ def rakefile_location ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || "" end end - - # Run the +rake+ application. - def run - begin - handle_options - tasks = collect_tasks - load_rakefile - if options.show_tasks - display_tasks_and_comments - elsif options.show_prereqs - display_prerequisites - else - tasks.each { |task_name| self[task_name].invoke } - end - rescue SystemExit, GetoptLong::InvalidOption => ex - # Exit silently - exit(1) - rescue Exception => ex - # Exit with error message - $stderr.puts "rake aborted!" - $stderr.puts ex.message - if options.trace - $stderr.puts ex.backtrace.join("\n") - else - $stderr.puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || "" - $stderr.puts "(See full trace by running task with --trace)" - end - exit(1) - end - end end end diff --git a/test/test_application.rb b/test/test_application.rb index 3e9ed55eb..cd09f9c50 100644 --- a/test/test_application.rb +++ b/test/test_application.rb @@ -23,7 +23,7 @@ def setup end def test_constant_warning - err = capture_stderr do @app.const_warning("Task") end + err = capture_stderr do @app.instance_eval { const_warning("Task") } end assert_match(/warning/i, err) assert_match(/deprecated/i, err) assert_match(/Task/i, err) @@ -33,28 +33,30 @@ def test_display_tasks @app.options.show_task_pattern = // @app.last_comment = "COMMENT" @app.define_task(Rake::Task, "t") - out = capture_stdout do @app.display_tasks_and_comments end + out = capture_stdout do @app.instance_eval { display_tasks_and_comments } end assert_match(/^rake t/, out) assert_match(/# COMMENT/, out) end def test_finding_rakefile - assert @app.have_rakefile + assert @app.instance_eval { have_rakefile } assert_equal "rakefile", @app.rakefile.downcase end def test_not_finding_rakefile @app.instance_eval { @rakefiles = ['NEVER_FOUND'] } - assert ! @app.have_rakefile + assert( ! @app.instance_eval do have_rakefile end ) assert_nil @app.rakefile end def test_load_rakefile original_dir = Dir.pwd Dir.chdir("test/data/unittest") - @app.handle_options - @app.options.silent = true - @app.load_rakefile + @app.instance_eval do + handle_options + options.silent = true + load_rakefile + end assert_equal "rakefile", @app.rakefile.downcase assert_match(%r(unittest$), Dir.pwd) ensure @@ -64,9 +66,11 @@ def test_load_rakefile def test_load_rakefile_from_subdir original_dir = Dir.pwd Dir.chdir("test/data/unittest/subdir") - @app.handle_options - @app.options.silent = true - @app.load_rakefile + @app.instance_eval do + handle_options + options.silent = true + load_rakefile + end assert_equal "rakefile", @app.rakefile.downcase assert_match(%r(unittest$), Dir.pwd) ensure @@ -76,42 +80,52 @@ def test_load_rakefile_from_subdir def test_load_rakefile_not_found original_dir = Dir.pwd Dir.chdir("/") - @app.handle_options - @app.options.silent = true - ex = assert_raise(RuntimeError) do @app.load_rakefile end + @app.instance_eval do + handle_options + options.silent = true + end + ex = assert_raise(RuntimeError) do + @app.instance_eval do raw_load_rakefile end + end assert_match(/no rakefile found/i, ex.message) ensure Dir.chdir(original_dir) end def test_not_caring_about_finding_rakefile - @app.instance_eval { @rakefiles = [''] } - assert @app.have_rakefile + @app.instance_eval do @rakefiles = [''] end + assert(@app.instance_eval do have_rakefile end) assert_equal '', @app.rakefile end def test_loading_imports mock = flexmock("loader") mock.should_receive(:load).with("x.dummy").once - @app.add_loader("dummy", mock) - @app.add_import("x.dummy") - @app.load_imports + @app.instance_eval do + add_loader("dummy", mock) + add_import("x.dummy") + load_imports + end end def test_building_imported_files_on_demand mock = flexmock("loader") mock.should_receive(:load).with("x.dummy").once mock.should_receive(:make_dummy).with_no_args.once - @app.intern(Rake::Task, "x.dummy").enhance do mock.make_dummy end - @app.add_loader("dummy", mock) - @app.add_import("x.dummy") - @app.load_imports + @app.instance_eval do + intern(Rake::Task, "x.dummy").enhance do mock.make_dummy end + add_loader("dummy", mock) + add_import("x.dummy") + load_imports + end end def test_good_run ran = false @app.options.silent = true - @app.intern(Rake::Task, "default").enhance { ran = true } + @app.instance_eval do + intern(Rake::Task, "default").enhance { ran = true } + end @app.run assert ran end @@ -358,7 +372,9 @@ def test_classic_namespace def test_bad_option capture_stderr do - ex = assert_raise(GetoptLong::InvalidOption) { flags('--bad-option') } + ex = assert_raise(GetoptLong::InvalidOption) do + flags('--bad-option') + end assert_match(/unrecognized option/, ex.message) assert_match(/--bad-option/, ex.message) end @@ -388,7 +404,7 @@ def flags(*sets) @out = capture_stdout { @exit = catch(:system_exit) { opts = command_line(*set) } } - yield(@app.options) + yield(@app.options) if block_given? end end @@ -398,8 +414,11 @@ def command_line(*options) def @app.exit(*args) throw :system_exit, :exit end - @app.handle_options - @tasks = @app.collect_tasks + @app.instance_eval do + handle_options + collect_tasks + end + @tasks = @app.top_level_tasks @app.options end end diff --git a/test/test_require.rb b/test/test_require.rb index b82456c81..1931b914e 100644 --- a/test/test_require.rb +++ b/test/test_require.rb @@ -8,18 +8,24 @@ class TestRequire < Test::Unit::TestCase def test_can_load_rake_library app = Rake::Application.new - assert app.rake_require("test2", ['test/data/rakelib'], []) + assert app.instance_eval { + rake_require("test2", ['test/data/rakelib'], []) + } end def test_wont_reload_rake_library app = Rake::Application.new - assert ! app.rake_require("test2", ['test/data/rakelib'], ['test2']) + assert ! app.instance_eval { + rake_require("test2", ['test/data/rakelib'], ['test2']) + } end def test_throws_error_if_library_not_found app = Rake::Application.new ex = assert_raise(LoadError) { - assert app.rake_require("testx", ['test/data/rakelib'], []) + assert app.instance_eval { + rake_require("testx", ['test/data/rakelib'], []) + } } assert_match(/x/, ex.message) end