module Buildr module Java class CompileTask < Rake::Task # Compiler options, accessible from Compiler#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: # options.warnings = true # options.source = options.target = "1.6" class Options include Attributes OPTIONS = [:warnings, :deprecation, :source, :target, :lint, :debug, :other] # Generate warnings (opposite of -nowarn). inherited_attr :warnings # Output source locations where deprecated APIs are used. inherited_attr :deprecation # Provide source compatibility with specified release. inherited_attr :source # Generate class files for specific VM version. inherited_attr :target # Values to pass to Xlint: string or array. Use true to enable # Xlint with no values. inherited_attr :lint # Generate all debugging info. inherited_attr :debug # Array of arguments passed to the Java compiler as is. inherited_attr :other def initialize(parent = nil) @parent = parent end # :nodoc: attr_reader :parent 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) 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, :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, :verbose=>false end end # Source directories and files to compile. attr_accessor :sources # Adds source directories and files to compile, and returns self. # # For example: # compile.from("src/java").into("classes").with("module1.jar") def from(*files) @sources |= files.flatten self end # Classpath dependencies. attr_accessor :classpath # 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(*files) @classpath |= artifacts(files.flatten).uniq self end # The target directory for the generated class files. attr_reader :target # 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) def into(dir) dir = File.expand_path(dir) unless @target == dir @target = dir file(dir).enhance [self] end self end # Returns the compiler options. attr_reader :options # 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() # If we compiled successfully, then the target directory reflects that. # If we didn't, see needed? @timestamp ||= (File.exist?(target) ? File.stat(target).mtime : Rake::EARLY) end def needed?() return false if source_files.empty? return true unless File.exist?(target) 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() super + classpath + sources end def invoke_prerequisites() prerequisites.each { |n| application[n, @scope].invoke } end protected # 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 if File.directory?(source) base = Pathname.new(source) FileList[File.join(source, "**", "*.java")]. each { |file| map[file] = File.join(target, Pathname.new(file).relative_path_from(base).to_s.ext(".class")) } else map[source] = File.join(target, File.basename(source).ext(".class")) end map end 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(task("compile")) class Project # The source directory. The default is "src". inherited_attr :src_dir, "src" # The Java source code directory. The default is /main/java. inherited_attr :java_src_dir do File.join(src_dir, "main", "java") end # The resources source directory. The default is /main/resources. inherited_attr :resources_dir do File.join(src_dir, "main", "resources") end # The target directory. The default is "target". inherited_attr :target_dir, "target" # The Java target directory. The default is /classes. inherited_attr :java_target_dir do File.join(target_dir, "classes") end # 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. # # By default the prepare task will only generate the #target_dir directory. # # For example: # prepare "src/generated" # prepare { javacc.run } def prepare(*tasks, &block) task("prepare").enhance tasks, &block end # The compile task does what its name suggests. This method returns the project's # compile task. It also accepts a list of source directories and files to compile # (equivalent to calling Java::CompileTask#from on the task), and a block for any # post-compilation work. # # For more information, see Java::CompileTask. # # The compile task will pick all the source files in the #java_src_dir directory, # and unless specified, compile into the #java_target_dir directory. It will pick # the default values for compiler options from the parent project's compile task. # # For example: # compile.options.source = "1.5" # compile("src").with("log4j:log4j:jar:1.2") # compile { backport(compile.target) } def compile(*sources, &block) task("compile").from(sources).enhance &block end # The resources task executes after compilation, and will 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 #resources_dir into the # same target directory as the #compile task. # # For more information, see Filter. def resources(*tasks, &block) task("resources").enhance tasks, &block end end Project.on_define do |project| prepare = task("prepare") # Resources task is a filter. resources = FilterTask.define_task("resources") # Compile task requires prepare and performs resources, if anything compiled. compile = Java::CompileTask.define_task("compile"=>prepare) { |task| resources.invoke } project.recursive_task("compile") project.enhance do |project| # For convenience, have the prepare task generate the target directory. #directory project.path_to(:target_dir) #project.prepare.prerequisites.unshift project.path_to(:target_dir) # Use the source directory if exists, and set the target directory is not already set. project.compile.from project.path_to(:java_src_dir) if File.exist?(project.path_to(:java_src_dir)) project.compile.into project.path_to(:java_target_dir) unless project.compile.target # Do the same for resources. project.resources.include project.path_to(:resources_dir, "*") if File.exists?(project.path_to(:resources_dir)) project.resources.into project.compile.target unless project.resources.target # Now we know what to build/clean. project.build project.compile.target project.clean { verbose(false) { rm_rf project.path_to(:target_dir) } } end end end