require "core/project" require "core/build" require "java/artifact" require "java/java" require "tasks/filter" module Buildr module Java # Wraps Javac in a task that does all the heavy lifting. # # Accepts multiple source directories that are invoked as prerequisites before compilation. # You can pass a task as a source directory, e.g. compile.from(apt). # # Likewise, classpath dependencies are invoked before compiling. All classpath dependencies # are evaluated as #artifacts, so you can pass artifact specifications and even projects. # # Creates a file task for the target directory, so executing that task as a dependency will # execute the compile task first. # # Compiler options are inherited form a parent task, e.g. the foo:bar:compile task inherits # its options from the foo:compile task. Even if foo is an empty project that does not compile # any classes itself, you can use it to set compile options for all its sub-projects. # # Normally, the project will take care of setting the source and target directory, and you # only need to set options and classpath dependencies. See Project#compile. class CompileTask < Rake::Task # Compiler options, accessible from CompileTask#options. # # Supported options are: # - warnings -- Generate warnings if true (opposite of -nowarn). # - deprecation -- Output source locations where deprecated APIs are used. # - source -- Source compatibility with specified release. # - target -- Class file compatibility with specified release. # - lint -- Value to pass to xlint argument. Use true to enable default lint # options, or pass a specific setting as string or array of strings. # - debug -- Generate debugging info. # - other -- Array of options to pass to the Java compiler as is. # # For example: # compile.options.warnings = true # compile.options.source = options.target = "1.6" class Options include InheritedAttributes OPTIONS = [:warnings, :deprecation, :source, :target, :lint, :debug, :other] # Generate warnings (opposite of -nowarn). attr_accessor :warnings inherited_attr :warnings # Output source locations where deprecated APIs are used. attr_accessor :deprecation inherited_attr :deprecation # Provide source compatibility with specified release. attr_accessor :source inherited_attr :source # Generate class files for specific VM version. attr_accessor :target inherited_attr :target # Values to pass to Xlint: string or array. Use true to enable # Xlint with no values. attr_accessor :lint inherited_attr :lint # Generate all debugging info. attr_accessor :debug inherited_attr :debug # Array of arguments passed to the Java compiler as is. attr_accessor :other inherited_attr :other def initialize(parent = nil) #:nodoc: @parent = parent end attr_reader :parent # :nodoc: # Resets all the options. def clear() OPTIONS.each { |name| send "#{name}=", nil } end def to_s() OPTIONS.inject({}){ |hash, name| hash[name] = send(name) ; hash }.reject{ |name,value| value.nil? }.inspect end # Returns Javac command line arguments from the set of options. def javac_args() args = [] args << "-nowarn" unless warnings && verbose args << "-verbose" if Rake.application.options.trace args << "-g" if debug args << "-deprecation" if deprecation args << "-source" << source.to_s if source args << "-target" << target.to_s if target case lint when Array args << "-Xlint:#{lint.join(',')}" when String args << "-Xlint:#{lint}" when true args << "-Xlint" end args.concat(other.to_a) if other args end end def initialize(*args) #:nodoc: super parent = Rake::Task["^compile"] if name[":"] # Only if in namespace if parent && parent.respond_to?(:options) @options = Options.new(parent.options) else @options = Options.new end @sources = [] @classpath = [] enhance do |task| mkpath target.to_s, :verbose=>false Java.javac source_files.keys, :sourcepath=>sources.map(&:to_s).select { |source| File.directory?(source) }.uniq, :classpath=>classpath, :output=>target, :javac_args=>options.javac_args, :name=>task.name # By touching the target we let other tasks know we did something, # and also prevent recompiling again for classpath dependencies. touch target.to_s, :verbose=>false end end # Source directories and files to compile. attr_accessor :sources # :call-seq: # from(*sources) => self # # Adds source directories and files to compile, and returns self. # # For example: # compile.from("src/java").into("classes").with("module1.jar") def from(*sources) @sources |= sources.flatten self end # Classpath dependencies. attr_accessor :classpath # :call-seq: # with(*artifacts) => self # # Adds files and artifacts as classpath dependencies, and returns self. # # Calls #artifacts on the arguments, so you can pass artifact specifications, # tasks, projects, etc. Use this rather than setting the classpath directly. # # For example: # compile.with("module1.jar", "log4j:log4j:jar:1.0", project("foo")) def with(*specs) @classpath |= artifacts(specs.flatten).uniq self end # The target directory for the generated class files. attr_reader :target # :call-seq: # into(path) => self # # Sets the target directory and returns self. This will also set the compile task # as a prerequisite to a file task on the target directory. # # For example: # compile(src_dir).into(target_dir).with(artifacts) # Both compile.invoke and file(target_dir).invoke will compile the source files. def into(path) path = File.expand_path(path.to_s) @target = file(path).enhance([self]) unless @target && @target.to_s == path self end # Returns the compiler options. attr_reader :options # :call-seq: # using(options) => self # # Sets the compiler options from a hash and returns self. # # For example: # compile.using(:warnings=>true, :source=>"1.5") def using(hash) hash.each { |k, v| options.send "#{k}=", v } self end def timestamp() #:nodoc: # If we compiled successfully, then the target directory reflects that. # If we didn't, see needed? target ? target.timestamp : Rake::EARLY end def needed?() #:nodoc: return false if source_files.empty? return true unless File.exist?(target.to_s) return true if source_files.any? { |j, c| !File.exist?(c) || File.stat(j).mtime > File.stat(c).mtime } oldest = source_files.map { |j, c| File.stat(c).mtime }.min return classpath.any? { |path| application[path].timestamp > oldest } end def prerequisites() #:nodoc: super + classpath + sources end def invoke_prerequisites() #:nodoc: prerequisites.each { |n| application[n, @scope].invoke } end # Returns the files to compile. This list is derived from the list of sources, # expanding directories into files, and includes only source files that are # newer than the corresponding class file. Includes all files if one or more # classpath dependency has been updated. def source_files() @source_files ||= @sources.map(&:to_s).inject({}) do |map, source| raise "Compile task #{name} has source files, but no target directory" unless target target_dir = target.to_s if File.directory?(source) base = Pathname.new(source) FileList["#{source}/**/*.java"]. each { |file| map[file] = File.join(target_dir, Pathname.new(file).relative_path_from(base).to_s.ext(".class")) } else map[source] = File.join(target_dir, File.basename(source).ext(".class")) end map end end end # The resources task is executed by the compile task to copy resource files over # to the target directory. You can enhance this task in the normal way, but mostly # you will use the task's filter. # # For example: # resources.filter.using "Copyright"=>"Acme Inc, 2007" class ResourcesTask < Rake::Task # Returns the filter used to copy resources over. See Buildr::Filter. attr_reader :filter def initialize(*args) #:nodoc: super @filter = Buildr::filter enhance { filter.run } end # :call-seq: # include(*files) => self # # Includes the specified files in the filter and returns self. def include(*files) filter.include *files self end # :call-seq: # exclude(*files) => self # # Excludes the specified files in the filter and returns self. def exclude(*files) filter.exclude *files self end def prerequisites() #:nodoc: super + filter.sources end end end # Local task to execute the compile task of the current project. # This task is not itself a compile task. desc "Compile all projects" Project.local_task("compile") { |name| "Compiling #{name}" } class Project # :call-seq: # prepare(*prereqs) => task # prepare(*prereqs) { |task| .. } => task # # The prepare task executes before the #compile task. Use it for pre-compilation # tasks, e.g. generating source code. # # This method returns the project's prepare task. It also accepts a list of # prerequisites and a block, used to enhance the prepare task. # # For example: # prepare { schema_to_java } def prepare(*prereqs, &block) task("prepare").enhance prereqs, &block end # :call-seq: # compile(*sources) => CompileTask # compile(*sources) { |task| .. } => CompileTask # # The compile task does what its name suggests. This method returns the project's # CompileTask. It also accepts a list of source directories and files to compile # (equivalent to calling CompileTask#from on the task), and a block for any # post-compilation work. # # The compile task will pick all the source files in the src/main/java directory, # and unless specified, compile them into the target/classes directory. It will pick # the default values for compiler options from the parent project's compile task. # # For example: # # Force target compatibility. # compile.options.source = "1.6" # # Include Apt-generated source files. # compile.from apt # # Include Log4J and the api sub-project artifacts. # compile.with "log4j:log4j:jar:1.2", project("api") # # Run the OpenJPA bytecode enhancer after compilation. # compile { open_jpa_enhance } # # For more information, see Java::CompileTask. def compile(*sources, &block) task("compile").from(sources).enhance &block end # :call-seq: # resources(*prereqs) => ResourcesTask # resources(*prereqs) { |task| .. } => ResourcesTask # # The resources task is executed by the compile task to copy resources files # from the resource directory into the target directory. # # This method returns the project's resources task. It also accepts a list of # prerequisites and a block, used to enhance the resources task. # # By default the resources task copies files from the src/main/resources into the # same target directory as the #compile task. It does so using a filter that you # can access by calling resources.filter (see Buildr::Filter). # # For example: # resources.filter.include "config.xml" # resources.filter.using "Copyright"=>"Acme Inc, 2007" def resources(*prereqs, &block) task("resources").enhance prereqs, &block end end Project.on_define do |project| prepare = task("prepare") # Resources task is a filter. resources = Java::ResourcesTask.define_task("resources") project.path_to("src/main/resources").tap { |dir| resources.filter.include project.path_to(dir, "*") if File.exist?(dir) } # Compile task requires prepare and performs resources, if anything compiled. compile = Java::CompileTask.define_task("compile"=>[prepare, resources]) { |task| project.resources.invoke } project.path_to("src/main/java").tap { |dir| compile.from dir if File.exist?(dir) } compile.into project.path_to("target/classes") resources.filter.into project.compile.target project.recursive_task("compile") project.clean { verbose(false) { rm_rf project.path_to("target") } } project.enhance do |project| project.build project.compile.target end end end