# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with this # work for additional information regarding copyright ownership. The ASF # licenses this file to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) # Return now at the resolution that the current filesystem supports # Avoids scenario where Time.now and Time.now + 1 have same value on filesystem def now_at_fs_resolution test_filename = "#{Dir.pwd}/deleteme" FileUtils.touch test_filename File.atime(test_filename) end module CompilerHelper def compile_task @compile_task ||= define('foo').compile.using(:javac) end def compile_task_without_compiler @compile_task ||= define('foo').compile end def file_task @file_taks ||= define('bar').file('src') end def sources @sources ||= ['Test1.java', 'Test2.java'].map { |f| File.join('src/main/java/thepackage', f) }. each { |src| write src, "package thepackage; class #{src.pathmap('%n')} {}" } end def jars @jars ||= begin write 'jars/src/main/java/Dependency.java', 'class Dependency { }' define 'jars', :version=>'1.0', :base_dir => 'jars' do package(:jar, :id=>'jar1') package(:jar, :id=>'jar2') end project('jars').packages.map(&:to_s) end end end describe Buildr::CompileTask do include CompilerHelper it 'should respond to from() and return self' do compile_task.from(sources).should be(compile_task) end it 'should respond to from() with FileTask having no compiler set and return self' do compile_task_without_compiler.from(file_task).should be(compile_task) end it 'should respond to from() and add sources' do compile_task.from sources, File.dirname(sources.first) compile_task.sources.should == sources + [File.dirname(sources.first)] end it 'should respond to with() and return self' do compile_task.with('test.jar').should be(compile_task) end it 'should respond to with() and add dependencies' do jars = (1..3).map { |i| "test#{i}.jar" } compile_task.with *jars compile_task.dependencies.should == artifacts(jars) end it 'should respond to into() and return self' do compile_task.into('code').should be(compile_task) end it 'should respond to into() and create file task' do compile_task.from(sources).into('code') lambda { file('code').invoke }.should run_task('foo:compile') end it 'should respond to using() and return self' do compile_task.using(:source=>'1.4').should eql(compile_task) end it 'should respond to using() and set options' do compile_task.using(:source=>'1.4', 'target'=>'1.5') compile_task.options.source.should eql('1.4') compile_task.options.target.should eql('1.5') end it 'should attempt to identify compiler' do Compiler.compilers.first.should_receive(:applies_to?).at_least(:once) define('foo') end it 'should only support existing compilers' do lambda { define('foo') { compile.using(:unknown) } }.should raise_error(ArgumentError, /unknown compiler/i) end it 'should allow overriding the guessed compiler' do write "src/main/java/com/example/Hello.java", "" old_compiler = nil new_compiler = nil define('foo') { old_compiler = compile.compiler compile.using(:scalac) new_compiler = compile.compiler } old_compiler.should == :javac new_compiler.should == :scalac end end describe Buildr::CompileTask, '#compiler' do it 'should be nil if no compiler identifier' do define('foo').compile.compiler.should be_nil end it 'should return the selected compiler' do define('foo') { compile.using(:javac) } project('foo').compile.compiler.should eql(:javac) end it 'should attempt to identify compiler if sources are specified' do define 'foo' do Compiler.compilers.first.should_receive(:applies_to?).at_least(:once) compile.from('sources').compiler end end it 'should allow suppressing compilation' do write 'src/main/java/package/Test.java', 'class Test {}' define 'foo' do compile.sources.clear end project('foo').compile.invoke Dir['target/classes/*'].should be_empty end end describe Buildr::CompileTask, '#language' do it 'should be nil if no compiler identifier' do define('foo').compile.language.should be_nil end it 'should return the appropriate language' do define('foo') { compile.using(:javac) } project('foo').compile.language.should eql(:java) end end describe Buildr::CompileTask, '#sources' do include CompilerHelper it 'should be empty if no sources in default directory' do compile_task.sources.should be_empty end it 'should point to default directory if it contains sources' do write 'src/main/java', '' compile_task.sources.first.should point_to_path('src/main/java') end it 'should be an array' do compile_task.sources += sources compile_task.sources.should == sources end it 'should allow files' do compile_task.from(sources).into('classes').invoke sources.each { |src| file(src.pathmap('classes/thepackage/%n.class')).should exist } end it 'should allow directories' do compile_task.from(File.dirname(sources.first)).into('classes').invoke sources.each { |src| file(src.pathmap('classes/thepackage/%n.class')).should exist } end it 'should allow tasks' do lambda { compile_task.from(file(sources.first)).into('classes').invoke }.should run_task('foo:compile') end it 'should act as prerequisites' do file('src2') { |task| task('prereq').invoke ; mkpath task.name } lambda { compile_task.from('src2').into('classes').invoke }.should run_task('prereq') end end describe Buildr::CompileTask, '#dependencies' do include CompilerHelper it 'should be empty' do compile_task.dependencies.should be_empty end it 'should be an array' do compile_task.dependencies += jars compile_task.dependencies.should == jars end it 'should allow files' do compile_task.from(sources).with(jars).into('classes').invoke sources.each { |src| file(src.pathmap('classes/thepackage/%n.class')).should exist } end it 'should allow tasks' do compile_task.from(sources).with(file(jars.first)).into('classes').invoke end it 'should allow artifacts' do artifact('group:id:jar:1.0') { |task| mkpath File.dirname(task.to_s) ; cp jars.first.to_s, task.to_s }.enhance jars compile_task.from(sources).with('group:id:jar:1.0').into('classes').invoke end it 'should allow projects' do define('bar', :version=>'1', :group=>'self') { package :jar } compile_task.with project('bar') compile_task.dependencies.should == project('bar').packages end end describe Buildr::CompileTask, '#target' do include CompilerHelper it 'should be a file task' do compile_task.from(@sources).into('classes') compile_task.target.should be_kind_of(Rake::FileTask) end it 'should accept a task' do task = file('classes') compile_task.into(task).target.should be(task) end it 'should create dependency in file task when set' do compile_task.from(sources).into('classes') lambda { file('classes').invoke }.should run_task('foo:compile') end end describe Buildr::CompileTask, '#options' do include CompilerHelper it 'should have getter and setter methods' do compile_task.options.foo = 'bar' compile_task.options.foo.should eql('bar') end it 'should have bracket accessors' do compile_task.options[:foo] = 'bar' compile_task.options[:foo].should eql('bar') end it 'should map from bracket accessor to get/set accessor' do compile_task.options[:foo] = 'bar' compile_task.options.foo.should eql('bar') end it 'should be independent of parent' do define 'foo' do compile.using(:javac, :source=>'1.4') define 'bar' do compile.using(:javac, :source=>'1.5') end end project('foo').compile.options.source.should eql('1.4') project('foo:bar').compile.options.source.should eql('1.5') end end describe Buildr::CompileTask, '#invoke' do include CompilerHelper it 'should compile into target directory' do compile_task.from(sources).into('code').invoke Dir['code/thepackage/*.class'].should_not be_empty end it 'should compile only once' do compile_task.from(sources) lambda { compile_task.target.invoke }.should run_task('foo:compile') lambda { compile_task.invoke }.should_not run_task('foo:compile') end it 'should compile if there are source files to compile' do lambda { compile_task.from(sources).invoke }.should run_task('foo:compile') end it 'should not compile unless there are source files to compile' do lambda { compile_task.invoke }.should_not run_task('foo:compile') end it 'should require source file or directory to exist' do lambda { compile_task.from('empty').into('classes').invoke }.should raise_error(RuntimeError, /Don't know how to build/) end it 'should run all source files as prerequisites' do mkpath 'src' file('src').should_receive :invoke_prerequisites compile_task.from('src').invoke end it 'should require dependencies to exist' do lambda { compile_task.from(sources).with('no-such.jar').into('classes').invoke }.should \ raise_error(RuntimeError, /Don't know how to build/) end it 'should run all dependencies as prerequisites' do file(File.expand_path('no-such.jar')) { |task| task('prereq').invoke } lambda { compile_task.from(sources).with('no-such.jar').into('classes').invoke }.should run_tasks(['prereq', 'foo:compile']) end it 'should force compilation if no target' do lambda { compile_task.from(sources).invoke }.should run_task('foo:compile') end it 'should force compilation if target empty' do time = now_at_fs_resolution mkpath compile_task.target.to_s File.utime(time - 1, time - 1, compile_task.target.to_s) lambda { compile_task.from(sources).invoke }.should run_task('foo:compile') end it 'should force compilation if sources newer than compiled' do # Simulate class files that are older than source files. time = now_at_fs_resolution sources.each { |src| File.utime(time + 1, time + 1, src) } sources.map { |src| src.pathmap("#{compile_task.target}/thepackage/%n.class") }. each { |kls| write kls ; File.utime(time, time, kls) } File.utime(time - 1, time - 1, project('foo').compile.target.to_s) lambda { compile_task.from(sources).invoke }.should run_task('foo:compile') end it 'should not force compilation if sources older than compiled' do # When everything has the same timestamp, nothing is compiled again. time = now_at_fs_resolution sources.map { |src| File.utime(time, time, src); src.pathmap("#{compile_task.target}/thepackage/%n.class") }. each { |kls| write kls ; File.utime(time, time, kls) } lambda { compile_task.from(sources).invoke }.should_not run_task('foo:compile') end it 'should not force compilation if dependencies older than compiled' do jars; project('jars').task("package").invoke time = now_at_fs_resolution jars.each { |jar| File.utime(time - 1 , time - 1, jar) } sources.map { |src| File.utime(time, time, src); src.pathmap("#{compile_task.target}/thepackage/%n.class") }. each { |kls| write kls ; File.utime(time, time, kls) } lambda { compile_task.from(sources).with(jars).invoke }.should_not run_task('foo:compile') end it 'should force compilation if dependencies newer than compiled' do jars; project('jars').task("package").invoke # On my machine the times end up the same, so need to push dependencies in the past. time = now_at_fs_resolution sources.map { |src| src.pathmap("#{compile_task.target}/thepackage/%n.class") }. each { |kls| write kls ; File.utime(time - 1, time - 1, kls) } File.utime(time - 1, time - 1, project('foo').compile.target.to_s) jars.each { |jar| File.utime(time + 1, time + 1, jar) } lambda { compile_task.from(sources).with(jars).invoke }.should run_task('foo:compile') end it 'should timestamp target directory if specified' do time = now_at_fs_resolution - 10 mkpath compile_task.target.to_s File.utime(time, time, compile_task.target.to_s) compile_task.timestamp.should be_within(1).of(time) end it 'should touch target if anything compiled' do mkpath compile_task.target.to_s File.utime(now_at_fs_resolution - 10, now_at_fs_resolution - 10, compile_task.target.to_s) compile_task.from(sources).invoke File.stat(compile_task.target.to_s).mtime.should be_within(2).of(now_at_fs_resolution) end it 'should not touch target if nothing compiled' do mkpath compile_task.target.to_s File.utime(now_at_fs_resolution - 10, now_at_fs_resolution - 10, compile_task.target.to_s) compile_task.invoke File.stat(compile_task.target.to_s).mtime.should be_within(2).of(now_at_fs_resolution - 10) end it 'should not touch target if failed to compile' do mkpath compile_task.target.to_s File.utime(now_at_fs_resolution - 10, now_at_fs_resolution - 10, compile_task.target.to_s) write 'failed.java', 'not a class' suppress_stdout { compile_task.from('failed.java').invoke rescue nil } File.stat(compile_task.target.to_s).mtime.should be_within(2).of(now_at_fs_resolution - 10) end it 'should complain if source directories and no compiler selected' do mkpath 'sources' define 'bar' do lambda { compile.from('sources').invoke }.should raise_error(RuntimeError, /no compiler selected/i) end end it 'should not unnecessarily recompile files explicitly added to compile list (BUILDR-611)' do mkpath 'src/other' write 'src/other/Foo.java', 'package foo; public class Foo {}' compile_task.from FileList['src/other/**.java'] mkpath 'target/classes/foo' touch 'target/classes/foo/Foo.class' File.utime(now_at_fs_resolution - 10, now_at_fs_resolution - 10, compile_task.target.to_s) compile_task.invoke File.stat(compile_task.target.to_s).mtime.should be_within(2).of(now_at_fs_resolution - 10) end end RSpec.shared_examples 'accessor task' do it 'should return a task' do define('foo').send(@task_name).should be_kind_of(Rake::Task) end it 'should always return the same task' do task_name, task = @task_name, nil define('foo') { task = self.send(task_name) } project('foo').send(task_name).should be(task) end it 'should be unique for the project' do define('foo') { define 'bar' } project('foo').send(@task_name).should_not eql(project('foo:bar').send(@task_name)) end it 'should be named after the project' do define('foo') { define 'bar' } project('foo:bar').send(@task_name).name.should eql("foo:bar:#{@task_name}") end end describe Project, '#compile' do before { @task_name = 'compile' } it_should_behave_like 'accessor task' it 'should return a compile task' do define('foo').compile.should be_instance_of(CompileTask) end it 'should accept sources and add to source list' do define('foo') { compile('file1', 'file2') } project('foo').compile.sources.should include('file1', 'file2') end it 'should accept block and enhance task' do write 'src/main/java/Test.java', 'class Test {}' action = task('action') define('foo') { compile { action.invoke } } lambda { project('foo').compile.invoke }.should run_tasks('foo:compile', action) end it 'should execute resources task' do define 'foo' lambda { project('foo').compile.invoke }.should run_task('foo:resources') end it 'should be recursive' do write 'bar/src/main/java/Test.java', 'class Test {}' define('foo') { define 'bar' } lambda { project('foo').compile.invoke }.should run_task('foo:bar:compile') end it 'should be a local task' do write 'bar/src/main/java/Test.java', 'class Test {}' define('foo') { define 'bar' } lambda do in_original_dir project('foo:bar').base_dir do task('compile').invoke end end.should run_task('foo:bar:compile').but_not('foo:compile') end it 'should run from build task' do write 'bar/src/main/java/Test.java', 'class Test {}' define('foo') { define 'bar' } lambda { task('build').invoke }.should run_task('foo:bar:compile') end it 'should clean after itself' do mkpath 'code' define('foo') { compile.into('code') } lambda { task('clean').invoke }.should change { File.exist?('code') }.to(false) end end describe Project, '#resources' do before { @task_name = 'resources' } it_should_behave_like 'accessor task' it 'should return a resources task' do define('foo').resources.should be_instance_of(ResourcesTask) end it 'should provide a filter' do define('foo').resources.filter.should be_instance_of(Filter) end it 'should include src/main/resources as source directory' do write 'src/main/resources/test' define('foo').resources.sources.first.should point_to_path('src/main/resources') end it 'should include src/main/resources directory only if it exists' do define('foo').resources.sources.should be_empty end it 'should accept prerequisites' do tasks = ['task1', 'task2'].each { |name| task(name) } define('foo') { resources 'task1', 'task2' } lambda { project('foo').resources.invoke }.should run_tasks('task1', 'task2') end it 'should respond to from and add additional sources' do write 'src/main/resources/original' write 'extra/spicy' define('foo') { resources.from 'extra' } project('foo').resources.invoke FileList['target/resources/*'].sort.should == ['target/resources/original', 'target/resources/spicy'] end it 'should pass include pattern to filter' do 3.times { |i| write "src/main/resources/test#{i + 1}" } define('foo') { resources.include('test2') } project('foo').resources.invoke FileList['target/resources/*'].should == ['target/resources/test2'] end it 'should pass exclude pattern to filter' do 3.times { |i| write "src/main/resources/test#{i + 1}" } define('foo') { resources.exclude('test2') } project('foo').resources.invoke FileList['target/resources/*'].sort.should == ['target/resources/test1', 'target/resources/test3'] end it 'should accept block and enhance task' do action = task('action') define('foo') { resources { action.invoke } } lambda { project('foo').resources.invoke }.should run_tasks('foo:resources', action) end it 'should set target directory to target/resources' do write 'src/main/resources/foo' define('foo').resources.target.to_s.should point_to_path('target/resources') end it 'should use provided target directoy' do define('foo') { resources.filter.into('the_resources') } project('foo').resources.target.to_s.should point_to_path('the_resources') end it 'should create file task for target directory' do write 'src/main/resources/foo' define 'foo' project('foo').file('target/resources').invoke file('target/resources/foo').should exist end it 'should copy resources to target directory' do write 'src/main/resources/foo', 'Foo' define('foo').compile.invoke file('target/resources/foo').should contain('Foo') end it 'should copy new resources to target directory' do time = now_at_fs_resolution mkdir_p 'target/resources' File.utime(time-10, time-10, 'target/resources') write 'src/main/resources/foo', 'Foo' define('foo') project('foo').file('target/resources').invoke file('target/resources/foo').should exist end it 'should copy updated resources to target directory' do time = now_at_fs_resolution mkdir_p 'target/resources' write 'target/resources/foo', 'Foo' File.utime(time-10, time-10, 'target/resources') File.utime(time-10, time-10, 'target/resources/foo') write 'src/main/resources/foo', 'Foo2' define('foo') project('foo').file('target/resources').invoke file('target/resources/foo').should contain('Foo2') end it 'should not create target directory unless there are resources' do define('foo').compile.invoke file('target/resources').should_not exist end it 'should run from target/resources' do write 'src/main/resources/test' define('foo') lambda { project('foo').resources.target.invoke }.should change { File.exist?('target/resources/test') }.to(true) end it 'should not be recursive' do define('foo') { define 'bar' } lambda { project('foo').resources.invoke }.should_not run_task('foo:bar:resources') end it 'should use current profile for filtering' do write 'profiles.yaml', <<-YAML development: filter: foo: bar test: filter: foo: baz YAML write 'src/main/resources/foo', '${foo}' define('foo').compile.invoke file('target/resources/foo').should contain('bar') end it 'should use current profile as default for filtering' do write 'profiles.yaml', <<-YAML development: filter: foo: bar YAML write 'src/main/resources/foo', '${foo} ${baz}' define('foo') do resources.filter.using 'baz' => 'qux' end project('foo').compile.invoke file('target/resources/foo').should contain('bar qux') end it 'should allow clearing default filter mapping' do write 'profiles.yaml', <<-YAML development: filter: foo: bar YAML write 'src/main/resources/foo', '${foo} ${baz}' define('foo') do resources.filter.mapping.clear resources.filter.using 'baz' => 'qux' end project('foo').compile.invoke file('target/resources/foo').should contain('${foo} qux') end end