PageRenderTime 108ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/buildr/lib/buildr/core/compile.rb

https://github.com/NUBIC/shenandoah
Ruby | 608 lines | 283 code | 91 blank | 234 comment | 27 complexity | d2cf4a6e49a46020c9bfb2551f818cc6 MD5 | raw file
Possible License(s): Apache-2.0
  1. # Licensed to the Apache Software Foundation (ASF) under one or more
  2. # contributor license agreements. See the NOTICE file distributed with this
  3. # work for additional information regarding copyright ownership. The ASF
  4. # licenses this file to you under the Apache License, Version 2.0 (the
  5. # "License"); you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations under
  14. # the License.
  15. require 'buildr/core/common'
  16. module Buildr
  17. # The underlying compiler used by CompileTask.
  18. # To add a new compiler, extend Compiler::Base and add your compiler using:
  19. # Buildr::Compiler.add MyCompiler
  20. module Compiler
  21. class << self
  22. # Returns true if the specified compiler exists.
  23. def has?(name)
  24. compilers.any? { |compiler| compiler.to_sym == name.to_sym }
  25. end
  26. # Select a compiler by its name.
  27. def select(name)
  28. compilers.detect { |compiler| compiler.to_sym == name.to_sym }
  29. end
  30. # Adds a compiler to the list of supported compiler.
  31. #
  32. # For example:
  33. # Buildr::Compiler << Buildr::Javac
  34. def add(compiler)
  35. @compilers ||= []
  36. @compilers |= [compiler]
  37. end
  38. alias :<< :add
  39. # Returns a list of available compilers.
  40. def compilers
  41. @compilers ||= []
  42. end
  43. end
  44. # Base class for all compilers, with common functionality. Extend and over-ride as you see fit
  45. # (see Javac as an example).
  46. class Base #:nodoc:
  47. class << self
  48. # The compiler's identifier (e.g. :javac). Inferred from the class name.
  49. def to_sym
  50. @symbol ||= name.split('::').last.downcase.to_sym
  51. end
  52. # The compiled language (e.g. :java).
  53. attr_reader :language
  54. # Source directories to use if none were specified (e.g. 'java'). Defaults to #language.
  55. attr_reader :sources
  56. # Extension for source files (e.g. 'java'). Defaults to language.
  57. attr_reader :source_ext
  58. # The target path (e.g. 'classes')
  59. attr_reader :target
  60. # Extension for target files (e.g. 'class').
  61. attr_reader :target_ext
  62. # The default packaging type (e.g. :jar).
  63. attr_reader :packaging
  64. # Returns true if this compiler applies to any source code found in the listed source
  65. # directories. For example, Javac returns true if any of the source directories contains
  66. # a .java file. The default implementation looks to see if there are any files in the
  67. # specified path with the extension #source_ext.
  68. def applies_to?(project, task)
  69. paths = task.sources + [sources].flatten.map { |src| Array(project.path_to(:source, task.usage, src.to_sym)) }
  70. paths.flatten!
  71. ext_glob = Array(source_ext).join(',')
  72. paths.any? { |path| !Dir["#{path}/**/*.{#{ext_glob}}"].empty? }
  73. end
  74. # Implementations can use this method to specify various compiler attributes.
  75. # For example:
  76. # specify :language=>:java, :target=>'classes', :target_ext=>'class', :packaging=>:jar
  77. def specify(attrs)
  78. attrs[:sources] ||= attrs[:language].to_s
  79. attrs[:source_ext] ||= attrs[:language].to_s
  80. attrs.each { |name, value| instance_variable_set("@#{name}", value) }
  81. end
  82. # Returns additional dependencies required by this language. For example, since the
  83. # test framework picks on these, you can use the JUnit framework with Scala.
  84. # Defaults to obtaining a list of artifact specifications from the REQUIRES constant.
  85. def dependencies
  86. []
  87. end
  88. end
  89. # Construct a new compiler with the specified options. Note that options may
  90. # change before the compiler is run.
  91. def initialize(project, options)
  92. @project = project
  93. @options = options
  94. end
  95. # Options for this compiler.
  96. attr_reader :options
  97. # Determines if the compiler needs to run by checking if the target files exist,
  98. # and if any source files or dependencies are newer than corresponding target files.
  99. def needed?(sources, target, dependencies)
  100. map = compile_map(sources, target)
  101. return false if map.empty?
  102. return true unless File.exist?(target.to_s)
  103. source_files_not_yet_compiled = map.select { |source, target| !File.exist?(target) }.to_a
  104. trace "Compile needed because source file #{source_files_not_yet_compiled[0][0]} has no corresponding #{source_files_not_yet_compiled[0][1]}" unless source_files_not_yet_compiled.empty?
  105. return true if map.any? { |source, target| !File.exist?(target) || File.stat(source).mtime > File.stat(target).mtime }
  106. oldest = map.map { |source, target| File.stat(target).mtime }.min
  107. return dependencies.any? { |path| file(path).timestamp > oldest }
  108. end
  109. # Compile all files lists in sources (files and directories) into target using the
  110. # specified dependencies.
  111. def compile(sources, target, dependencies)
  112. raise 'Not implemented'
  113. end
  114. # Returns additional dependencies required by this language. For example, since the
  115. # test framework picks on these, you can use the JUnit framework with Scala.
  116. def dependencies
  117. self.class.dependencies
  118. end
  119. protected
  120. # Use this to complain about CompileTask options not supported by this compiler.
  121. #
  122. # For example:
  123. # def compile(files, task)
  124. # check_options task, OPTIONS
  125. # . . .
  126. # end
  127. def check_options(options, *supported)
  128. unsupported = options.to_hash.keys - supported.flatten
  129. raise ArgumentError, "No such option: #{unsupported.join(' ')}" unless unsupported.empty?
  130. end
  131. # Expands a list of source directories/files into a list of files that have the #source_ext extension.
  132. def files_from_sources(sources)
  133. ext_glob = Array(self.class.source_ext).join(',')
  134. sources.flatten.map { |source| File.directory?(source) ? FileList["#{source}/**/*.{#{ext_glob}}"] : source }.
  135. flatten.reject { |file| File.directory?(file) }.map { |file| File.expand_path(file) }.uniq
  136. end
  137. # The compile map is a hash that associates source files with target files based
  138. # on a list of source directories and target directory. The compile task uses this
  139. # to determine if there are source files to compile, and which source files to compile.
  140. # The default method maps all files in the source directories with #source_ext into
  141. # paths in the target directory with #target_ext (e.g. 'source/foo.java'=>'target/foo.class').
  142. def compile_map(sources, target)
  143. target_ext = self.class.target_ext
  144. ext_glob = Array(self.class.source_ext).join(',')
  145. sources.flatten.map{|f| File.expand_path(f)}.inject({}) do |map, source|
  146. if File.directory?(source)
  147. FileList["#{source}/**/*.{#{ext_glob}}"].reject { |file| File.directory?(file) }.
  148. each { |file| map[file] = File.join(target, Util.relative_path(file, source).ext(target_ext)) }
  149. else
  150. # try to extract package name from .java or .scala files
  151. if ['.java', '.scala', '.groovy'].include? File.extname(source)
  152. package = findFirst(source, /^\s*package\s+(\S+)\s*;?\s*$/)
  153. map[source] = package ? File.join(target, package[1].gsub('.', '/'), File.basename(source).ext(target_ext)) : target
  154. elsif
  155. map[source] = target
  156. end
  157. end
  158. map
  159. end
  160. end
  161. private
  162. def findFirst(file, pattern)
  163. match = nil
  164. File.open(file, "r") do |infile|
  165. while (line = infile.gets)
  166. match = line.match(pattern)
  167. break if match
  168. end
  169. end
  170. match
  171. end
  172. end
  173. end
  174. # Compile task.
  175. #
  176. # Attempts to determine which compiler to use based on the project layout, for example,
  177. # uses the Javac compiler if it finds any .java files in src/main/java. You can also
  178. # select the compiler explicitly:
  179. # compile.using(:scalac)
  180. #
  181. # Accepts multiple source directories that are invoked as prerequisites before compilation.
  182. # You can pass a task as a source directory:
  183. # compile.from(apt)
  184. #
  185. # Likewise, dependencies are invoked before compiling. All dependencies are evaluated as
  186. # #artifacts, so you can pass artifact specifications and even projects:
  187. # compile.with('module1.jar', 'log4j:log4j:jar:1.0', project('foo'))
  188. #
  189. # Creates a file task for the target directory, so executing that task as a dependency will
  190. # execute the compile task first.
  191. #
  192. # Compiler options are inherited form a parent task, e.g. the foo:bar:compile task inherits
  193. # its options from the foo:compile task. Even if foo is an empty project that does not compile
  194. # any classes itself, you can use it to set compile options for all its sub-projects.
  195. #
  196. # Normally, the project will take care of setting the source and target directory, and you
  197. # only need to set options and dependencies. See Project#compile.
  198. class CompileTask < Rake::Task
  199. def initialize(*args) #:nodoc:
  200. super
  201. parent_task = Project.parent_task(name)
  202. inherit = lambda { |hash, key| parent_task.options[key] } if parent_task.respond_to?(:options)
  203. @options = OpenObject.new &inherit
  204. @sources = FileList[]
  205. @dependencies = FileList[]
  206. enhance do |task|
  207. unless sources.empty?
  208. raise 'No compiler selected and can\'t determine which compiler to use' unless compiler
  209. raise 'No target directory specified' unless target
  210. mkpath target.to_s
  211. info "Compiling #{task.name.gsub(/:[^:]*$/, '')} into #{target.to_s}"
  212. @compiler.compile(sources.map(&:to_s), target.to_s, dependencies.map(&:to_s))
  213. # By touching the target we let other tasks know we did something,
  214. # and also prevent recompiling again for dependencies.
  215. touch target.to_s
  216. end
  217. end
  218. end
  219. # Source directories.
  220. attr_accessor :sources
  221. # :call-seq:
  222. # from(*sources) => self
  223. #
  224. # Adds source directories and files to compile, and returns self.
  225. #
  226. # For example:
  227. # compile.from('src/java').into('classes').with('module1.jar')
  228. def from(*sources)
  229. @sources |= sources.flatten
  230. guess_compiler if @compiler.nil? && sources.flatten.any? { |source| File.exist?(source.to_s) }
  231. self
  232. end
  233. # *Deprecated*: Use dependencies instead.
  234. def classpath
  235. Buildr.application.deprecated 'Use dependencies instead.'
  236. dependencies
  237. end
  238. # *Deprecated*: Use dependencies= instead.
  239. def classpath=(artifacts)
  240. Buildr.application.deprecated 'Use dependencies= instead.'
  241. self.dependencies = artifacts
  242. end
  243. # Compilation dependencies.
  244. attr_accessor :dependencies
  245. # :call-seq:
  246. # with(*artifacts) => self
  247. #
  248. # Adds files and artifacts as dependencies, and returns self.
  249. #
  250. # Calls #artifacts on the arguments, so you can pass artifact specifications,
  251. # tasks, projects, etc. Use this rather than setting the dependencies array directly.
  252. #
  253. # For example:
  254. # compile.with('module1.jar', 'log4j:log4j:jar:1.0', project('foo'))
  255. def with(*specs)
  256. @dependencies |= Buildr.artifacts(specs.flatten).uniq
  257. self
  258. end
  259. # The target directory for the compiled code.
  260. attr_reader :target
  261. # :call-seq:
  262. # into(path) => self
  263. #
  264. # Sets the target directory and returns self. This will also set the compile task
  265. # as a prerequisite to a file task on the target directory.
  266. #
  267. # For example:
  268. # compile(src_dir).into(target_dir).with(artifacts)
  269. # Both compile.invoke and file(target_dir).invoke will compile the source files.
  270. def into(path)
  271. @target = file(path.to_s).enhance([self]) unless @target.to_s == path.to_s
  272. self
  273. end
  274. # Returns the compiler options.
  275. attr_reader :options
  276. # :call-seq:
  277. # using(options) => self
  278. #
  279. # Sets the compiler options from a hash and returns self. Can also be used to
  280. # select the compiler.
  281. #
  282. # For example:
  283. # compile.using(:warnings=>true, :source=>'1.5')
  284. # compile.using(:scala)
  285. def using(*args)
  286. args.pop.each { |key, value| options.send "#{key}=", value } if Hash === args.last
  287. self.compiler = args.pop until args.empty?
  288. self
  289. end
  290. # Returns the compiler if known. The compiler is either automatically selected
  291. # based on existing source directories (e.g. src/main/java), or by requesting
  292. # a specific compiler (see #using).
  293. def compiler
  294. guess_compiler unless @compiler
  295. @compiler && @compiler.class.to_sym
  296. end
  297. # Returns the compiled language, if known. See also #compiler.
  298. def language
  299. compiler && @compiler.class.language
  300. end
  301. # Returns the default packaging type for this compiler, if known.
  302. def packaging
  303. compiler && @compiler.class.packaging
  304. end
  305. def timestamp #:nodoc:
  306. # If we compiled successfully, then the target directory reflects that.
  307. # If we didn't, see needed?
  308. target ? target.timestamp : Rake::EARLY
  309. end
  310. # The project this task belongs to.
  311. attr_reader :project
  312. # The usage, one of :main or :test.
  313. attr_reader :usage
  314. protected
  315. # Selects which compiler to use.
  316. def compiler=(name) #:nodoc:
  317. cls = Compiler.select(name) or raise ArgumentError, "No #{name} compiler available. Did you install it?"
  318. return self if cls === @compiler
  319. raise "#{compiler} compiler already selected for this project" if @compiler
  320. @compiler = cls.new(project, options)
  321. from Array(cls.sources).map { |path| project.path_to(:source, usage, path) }.
  322. select { |path| File.exist?(path) } if sources.empty?
  323. into project.path_to(:target, usage, cls.target) unless target
  324. with Array(@compiler.dependencies)
  325. self
  326. end
  327. # Associates this task with project and particular usage (:main, :test).
  328. def associate_with(project, usage) #:nodoc:
  329. @project, @usage = project, usage
  330. guess_compiler
  331. end
  332. # Try to guess if we have a compiler to match source files.
  333. def guess_compiler #:nodoc:
  334. candidate = Compiler.compilers.detect { |cls| cls.applies_to?(project, self) }
  335. self.compiler = candidate if candidate
  336. end
  337. private
  338. def needed? #:nodoc:
  339. return false if sources.empty?
  340. # Fail during invoke.
  341. return true unless @compiler && target
  342. return @compiler.needed?(sources.map(&:to_s), target.to_s, dependencies.map(&:to_s))
  343. end
  344. def invoke_prerequisites(args, chain) #:nodoc:
  345. @sources = Array(@sources).map(&:to_s).uniq
  346. @dependencies = FileList[@dependencies.uniq]
  347. @prerequisites |= @dependencies + @sources
  348. super
  349. end
  350. end
  351. # The resources task is executed by the compile task to copy resource files over
  352. # to the target directory. You can enhance this task in the normal way, but mostly
  353. # you will use the task's filter.
  354. #
  355. # For example:
  356. # resources.filter.using 'Copyright'=>'Acme Inc, 2007'
  357. class ResourcesTask < Rake::Task
  358. # Returns the filter used to copy resources over. See Buildr::Filter.
  359. attr_reader :filter
  360. def initialize(*args) #:nodoc:
  361. super
  362. @filter = Buildr::Filter.new
  363. @filter.using Buildr.settings.profile['filter'] if Hash === Buildr.settings.profile['filter']
  364. enhance do
  365. target.invoke if target
  366. end
  367. end
  368. # :call-seq:
  369. # include(*files) => self
  370. #
  371. # Includes the specified files in the filter and returns self.
  372. def include(*files)
  373. filter.include *files
  374. self
  375. end
  376. # :call-seq:
  377. # exclude(*files) => self
  378. #
  379. # Excludes the specified files in the filter and returns self.
  380. def exclude(*files)
  381. filter.exclude *files
  382. self
  383. end
  384. # :call-seq:
  385. # from(*sources) => self
  386. #
  387. # Adds additional directories from which to copy resources.
  388. #
  389. # For example:
  390. # resources.from _('src/etc')
  391. def from(*sources)
  392. filter.from *sources
  393. self
  394. end
  395. # Returns the list of source directories (each being a file task).
  396. def sources
  397. filter.sources
  398. end
  399. # :call-seq:
  400. # target => task
  401. #
  402. # Returns the filter's target directory as a file task.
  403. def target
  404. filter.into @project.path_to(:target, @usage, :resources) unless filter.target || sources.empty?
  405. filter.target
  406. end
  407. def prerequisites #:nodoc:
  408. super + filter.sources.flatten
  409. end
  410. protected
  411. # Associates this task with project and particular usage (:main, :test).
  412. def associate_with(project, usage) #:nodoc:
  413. @project, @usage = project, usage
  414. end
  415. end
  416. # Methods added to Project for compiling, handling of resources and generating source documentation.
  417. module Compile
  418. include Extension
  419. first_time do
  420. desc 'Compile all projects'
  421. Project.local_task('compile') { |name| "Compiling #{name}" }
  422. end
  423. before_define(:compile) do |project|
  424. resources = ResourcesTask.define_task('resources')
  425. resources.send :associate_with, project, :main
  426. project.path_to(:source, :main, :resources).tap { |dir| resources.from dir if File.exist?(dir) }
  427. compile = CompileTask.define_task('compile'=>resources)
  428. compile.send :associate_with, project, :main
  429. project.recursive_task('compile')
  430. end
  431. after_define(:compile) do |project|
  432. if project.compile.target
  433. # This comes last because the target path is set inside the project definition.
  434. project.build project.compile.target
  435. project.clean do
  436. rm_rf project.compile.target.to_s, :verbose=>false
  437. end
  438. end
  439. end
  440. # :call-seq:
  441. # compile(*sources) => CompileTask
  442. # compile(*sources) { |task| .. } => CompileTask
  443. #
  444. # The compile task does what its name suggests. This method returns the project's
  445. # CompileTask. It also accepts a list of source directories and files to compile
  446. # (equivalent to calling CompileTask#from on the task), and a block for any
  447. # post-compilation work.
  448. #
  449. # The compile task attempts to guess which compiler to use. For example, if it finds
  450. # any Java files in the src/main/java directory, it will use the Java compiler and
  451. # create class files in the target/classes directory.
  452. #
  453. # You can also configure it yourself by telling it which compiler to use, pointing
  454. # it as source directories and chooing a different target directory.
  455. #
  456. # For example:
  457. # # Include Log4J and the api sub-project artifacts.
  458. # compile.with 'log4j:log4j:jar:1.2', project('api')
  459. # # Include Apt-generated source files.
  460. # compile.from apt
  461. # # For JavaC, force target compatibility.
  462. # compile.options.source = '1.6'
  463. # # Run the OpenJPA bytecode enhancer after compilation.
  464. # compile { open_jpa_enhance }
  465. # # Pick a given compiler.
  466. # compile.using(:scalac).from('src/scala')
  467. #
  468. # For more information, see CompileTask.
  469. def compile(*sources, &block)
  470. task('compile').from(sources).enhance &block
  471. end
  472. # :call-seq:
  473. # resources(*prereqs) => ResourcesTask
  474. # resources(*prereqs) { |task| .. } => ResourcesTask
  475. #
  476. # The resources task is executed by the compile task to copy resources files
  477. # from the resource directory into the target directory. By default the resources
  478. # task copies files from the src/main/resources into the target/resources directory.
  479. #
  480. # This method returns the project's resources task. It also accepts a list of
  481. # prerequisites and a block, used to enhance the resources task.
  482. #
  483. # Resources files are copied and filtered (see Buildr::Filter for more information).
  484. # The default filter uses the profile properties for the current environment.
  485. #
  486. # For example:
  487. # resources.from _('src/etc')
  488. # resources.filter.using 'Copyright'=>'Acme Inc, 2007'
  489. #
  490. # Or in your profiles.yaml file:
  491. # common:
  492. # Copyright: Acme Inc, 2007
  493. def resources(*prereqs, &block)
  494. task('resources').enhance prereqs, &block
  495. end
  496. end
  497. class Options
  498. # Returns the debug option (environment variable DEBUG).
  499. def debug
  500. (ENV['DEBUG'] || ENV['debug']) !~ /(no|off|false)/
  501. end
  502. # Sets the debug option (environment variable DEBUG).
  503. #
  504. # You can turn this option off directly, or by setting the environment variable
  505. # DEBUG to +no+. For example:
  506. # buildr build DEBUG=no
  507. #
  508. # The release tasks runs a build with <tt>DEBUG=no</tt>.
  509. def debug=(flag)
  510. ENV['debug'] = nil
  511. ENV['DEBUG'] = flag.to_s
  512. end
  513. end
  514. end
  515. class Buildr::Project
  516. include Buildr::Compile
  517. end