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). # - verbose -- Output messages about what the compiler is doing. # - 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.verbose = true # options.source = options.target = "1.6" class Options include Attributes OPTIONS = [:warnings, :verbose, :deprecation, :source, :target, :lint, :debug, :other] # Generate warnings (opposite of -nowarn). inherited_attr :warnings # Output messages about what the compiler is doing. inherited_attr :verbose # 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 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 && Rake.application.options.trace args << "-verbose" if verbose 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 << other if other args end end # The target directory for the generated class files. attr_accessor :target def initialize(*args) super if name[":"] # Only if in namespace parent = Rake::Task["^compile"] if parent && parent.respond_to?(:options) @options = Options.new(parent.options) end end enhance do |task| # Do we have any files to compile? if target && files.empty? puts "All source files are up to date" if Rake.application.options.trace && !sources.empty? elsif target mkpath target, :verbose=>false Java.javac files, :sourcepath=>sourcepath, :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 end # An array of source directories and files. def sources() @sources ||= [] end def sources=(paths) @sources = paths.flatten end # Array of classpath dependencies: files, file tasks, artifacts specs. def classpath() @classpath ||= [] end def classpath=(paths) @classpath = paths.flatten end # Returns the compiler options. def options() @options ||= Options.new end # Sets the compile options based on a hash of values, or reset to # the default by passing nil. def options=(arg) case arg when Options @options = arg when Hash options.clear arg.each { |k,v| options.send "#{k}=", v } when nil options.clear else raise ArgumentError, "Expecting Options, hash or nil (to reset)" end @options end # Sets the target directory and returns self. For example: # compile(src_dir).to(target_dir).with(artifacts) def to(dir) self.target = dir self end # Adds files and artifacts to the classpath and returns self. # For example: # compile(src_dir).to(target_dir).with(artifact, file, task) def with(*args) self.classpath |= args.flatten self end # Sets the compiler options and returns self. For example: # compile(src_dir).using(:warnings=>true, :verbose=>true) def using(hash) self.options = hash self end # Returns true if any classes were compiled. def compiled?() @files && !@files.empty? end protected # Returns the real classpath. Uses the values of #classpath, but resolves # artifact specifications, projects and other conveniences, executes tasks # (see #sanitize) and returns a compact array of unique file names. def real_classpath() @real_classpath ||= sanitize(artifacts(classpath)) end # Return the sourcepath, essentialy compact array of directory and file names, # as set by the user, but after invoking dependencies (see #sanitize). def sourcepath() @real_sources ||= sanitize(sources) @real_sources.select { |source| File.directory?(source) } 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 files() unless @files @real_sources ||= sanitize(sources) # Compile all files if we compiled nothing, or one of the classpath # dependencies is newer than the compiled classes. all = !File.exist?(target) || File.stat(target).mtime < (real_classpath.collect{ |file| File.stat(file).mtime } + [ Rake::EARLY ]).max if all # Do not restrict to changed files @files = @real_sources.collect do |source| File.directory?(source) ? Dir[File.join(source, "**", "*.java")] : source end.flatten else # Only changed files. @files = @real_sources.collect do |source| if File.directory?(source) Dir[File.join(source, "**", "*.java")].select do |java| klass = java.sub(source, target).ext(".class") !File.exist?(klass) || File.stat(java).mtime > File.stat(klass).mtime end else source end end.flatten end end @files end def sanitize(paths) # Flatten to handle nested arrays often used with dependencies. # Invoke all tasks, treated as prerequisites (e.g. download artifacts). # Return task name or file name, ignoring nils and duplicates. paths.flatten.each { |path| path.invoke if path.respond_to?(:invoke) }. collect { |path| path.respond_to?(:name) ? path.name : path.to_s }. compact.uniq end end end # Create and return a compiler task. The task is name "compile" in the current # namespace. Method arguments are passed as sources to compile, and options are # inherited from any compile task in a parent namespace. # # For example: # compile("src").to("classes").with(artifact, file, task) def self.compile(*sources) returning(Java::CompileTask.define_task("compile")) do |task| task.sources |= sources end end def compile(*sources) Buildr.compile(*sources) end # Global task compiles all projects. desc "Compile all projects" LocalDirectoryTask.define_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 def resources(*tasks, &block) returning(@resources_task ||= Filter.define_task("resources")) do |task| task.enhance tasks, &block end end def compile(*sources, &block) returning(@compile_task ||= Java::CompileTask.define_task("compile")) do |task| task.sources |= sources task.enhance &block if block end end def prepare(*tasks, &block) returning(@prepare_task ||= task("prepare")) do |task| task.enhance tasks, &block end end end Project.on_create do |project| # Prepare is prerequisite for compile, resources follows compile. project.compile.enhance([project.prepare]) { |task| project.resources.invoke } project.recursive_task("compile") task("build").enhance [ project.compile ] task("clean") { rm_rf project.path_to(:target_dir), :verbose=>false } project.enhance do |project| # Automagic compilation only if the source directory exists. project.compile.sources << project.path_to(:java_src_dir) if File.exist?(project.path_to(:java_src_dir)) project.compile.target ||= project.path_to(:java_target_dir) project.resources.include project.path_to(:resources_dir, "*") if File.exist?(project.path_to(:resources_dir)) project.resources.target ||= project.compile.target file(project.compile.target).enhance([ project.compile, project.resources ]) end end end