Simplify the add_web_facet implementation
[buildr.git] / lib / buildr / ide / idea.rb
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
16 module Buildr #:nodoc:
17 module IntellijIdea
18 def self.new_document(value)
19 REXML::Document.new(value, :attribute_quote => :quote)
20 end
21
22 # Abstract base class for IdeaModule and IdeaProject
23 class IdeaFile
24 DEFAULT_SUFFIX = ""
25 DEFAULT_LOCAL_REPOSITORY_ENV_OVERRIDE = "MAVEN_REPOSITORY"
26
27 attr_reader :buildr_project
28 attr_writer :suffix
29 attr_writer :id
30 attr_accessor :template
31 attr_accessor :local_repository_env_override
32
33 def initialize
34 @local_repository_env_override = DEFAULT_LOCAL_REPOSITORY_ENV_OVERRIDE
35 end
36
37 def suffix
38 @suffix ||= DEFAULT_SUFFIX
39 end
40
41 def filename
42 buildr_project.path_to("#{name}.#{extension}")
43 end
44
45 def id
46 @id ||= buildr_project.name.split(':').last
47 end
48
49 def add_component(name, attrs = {}, &xml)
50 self.components << create_component(name, attrs, &xml)
51 end
52
53 # IDEA can not handle text content with indents so need to removing indenting
54 # Can not pass true as third argument as the ruby library seems broken
55 def write(f)
56 document.write(f, -1, false, true)
57 end
58
59 protected
60
61 def name
62 "#{self.id}#{suffix}"
63 end
64
65 def relative(path)
66 ::Buildr::Util.relative_path(File.expand_path(path.to_s), self.base_directory)
67 end
68
69 def base_directory
70 buildr_project.path_to
71 end
72
73 def resolve_path_from_base(path, base_variable)
74 m2repo = Buildr::Repositories.instance.local
75 if path.to_s.index(m2repo) == 0 && !self.local_repository_env_override.nil?
76 return path.sub(m2repo, "$#{self.local_repository_env_override}$")
77 else
78 begin
79 return "#{base_variable}/#{relative(path)}"
80 rescue ArgumentError
81 # ArgumentError happens on windows when self.base_directory and path are on different drives
82 return path
83 end
84 end
85 end
86
87 def file_path(path)
88 "file://#{resolve_path(path)}"
89 end
90
91 def create_component(name, attrs = {})
92 target = StringIO.new
93 Builder::XmlMarkup.new(:target => target, :indent => 2).component({:name => name}.merge(attrs)) do |xml|
94 yield xml if block_given?
95 end
96 Buildr::IntellijIdea.new_document(target.string).root
97 end
98
99 def components
100 @components ||= []
101 end
102
103 def create_composite_component(name, components)
104 return nil if components.empty?
105 component = self.create_component(name)
106 components.each do |element|
107 element = element.call if element.is_a?(Proc)
108 component.add_element element
109 end
110 component
111 end
112
113 def add_to_composite_component(components)
114 components << lambda do
115 target = StringIO.new
116 yield Builder::XmlMarkup.new(:target => target, :indent => 2)
117 Buildr::IntellijIdea.new_document(target.string).root
118 end
119 end
120
121 def load_document(filename)
122 Buildr::IntellijIdea.new_document(File.read(filename))
123 end
124
125 def document
126 if File.exist?(self.filename)
127 doc = load_document(self.filename)
128 else
129 doc = base_document
130 inject_components(doc, self.initial_components)
131 end
132 if self.template
133 template_doc = load_document(self.template)
134 REXML::XPath.each(template_doc, "//component") do |element|
135 inject_component(doc, element)
136 end
137 end
138 inject_components(doc, self.default_components.compact + self.components)
139
140 # Sort the components in the same order the idea sorts them
141 sorted = doc.root.get_elements('//component').sort { |s1, s2| s1.attribute('name').value <=> s2.attribute('name').value }
142 doc = base_document
143 sorted.each do |element|
144 doc.root.add_element element
145 end
146
147 doc
148 end
149
150 def inject_components(doc, components)
151 components.each do |component|
152 # execute deferred components
153 component = component.call if Proc === component
154 inject_component(doc, component) if component
155 end
156 end
157
158 # replace overridden component (if any) with specified component
159 def inject_component(doc, component)
160 doc.root.delete_element("//component[@name='#{component.attributes['name']}']")
161 doc.root.add_element component
162 end
163 end
164
165 # IdeaModule represents an .iml file
166 class IdeaModule < IdeaFile
167 DEFAULT_TYPE = "JAVA_MODULE"
168
169 attr_accessor :type
170 attr_accessor :group
171 attr_reader :facets
172 attr_writer :jdk_version
173
174 def initialize
175 super()
176 @type = DEFAULT_TYPE
177 end
178
179 def buildr_project=(buildr_project)
180 @id = nil
181 @facets = []
182 @skip_content = false
183 @buildr_project = buildr_project
184 end
185
186 def jdk_version
187 @jdk_version || buildr_project.compile.options.source || "1.6"
188 end
189
190 def extension
191 "iml"
192 end
193
194 def main_source_directories
195 @main_source_directories ||= [
196 buildr_project.compile.sources,
197 buildr_project.resources.sources
198 ].flatten.compact
199 end
200
201 def test_source_directories
202 @test_source_directories ||= [
203 buildr_project.test.compile.sources,
204 buildr_project.test.resources.sources
205 ].flatten.compact
206 end
207
208 def excluded_directories
209 @excluded_directories ||= [
210 buildr_project.resources.target,
211 buildr_project.test.resources.target,
212 buildr_project.path_to(:target, :main),
213 buildr_project.path_to(:target, :test),
214 buildr_project.path_to(:reports)
215 ].flatten.compact
216 end
217
218 attr_writer :main_output_dir
219
220 def main_output_dir
221 @main_output_dir ||= buildr_project._(:target, :main, :idea, :classes)
222 end
223
224 attr_writer :test_output_dir
225
226 def test_output_dir
227 @test_output_dir ||= buildr_project._(:target, :test, :idea, :classes)
228 end
229
230 def main_dependencies
231 @main_dependencies ||= buildr_project.compile.dependencies
232 end
233
234 def test_dependencies
235 @test_dependencies ||= buildr_project.test.compile.dependencies
236 end
237
238 def add_facet(name, type)
239 add_to_composite_component(self.facets) do |xml|
240 xml.facet(:name => name, :type => type) do |xml|
241 yield xml if block_given?
242 end
243 end
244 end
245
246 def skip_content?
247 !!@skip_content
248 end
249
250 def skip_content!
251 @skip_content = true
252 end
253
254 def add_gwt_facet(modules = {}, options = {})
255 name = options[:name] || "GWT"
256 detected_gwt_version = nil
257 if options[:gwt_dev_artifact]
258 a = Buildr.artifact(options[:gwt_dev_artifact])
259 a.invoke
260 detected_gwt_version = a.to_s
261 end
262
263 settings =
264 {
265 :webFacet => "Web",
266 :compilerMaxHeapSize => "512",
267 :compilerParameters => "-draftCompile -localWorkers 2 -strict",
268 :gwtScriptOutputStyle => "PRETTY"
269 }.merge(options[:settings] || {})
270
271 buildr_project.compile.dependencies.each do |d|
272 if d.to_s =~ /\/com\/google\/gwt\/gwt-dev\/(.*)\//
273 detected_gwt_version = d.to_s
274 break
275 end
276 end unless detected_gwt_version
277
278 if detected_gwt_version
279 settings[:gwtSdkUrl] = resolve_path(File.dirname(detected_gwt_version))
280 settings[:gwtSdkType] = "maven"
281 else
282 settings[:gwtSdkUrl] = "file://$GWT_TOOLS$"
283 end
284
285 add_facet(name, "gwt") do |f|
286 f.configuration do |c|
287 settings.each_pair do |k, v|
288 c.setting :name => k.to_s, :value => v.to_s
289 end
290 c.packaging do |d|
291 modules.each_pair do |k, v|
292 d.module :name => k, :enabled => v
293 end
294 end
295 end
296 end
297 end
298
299 def add_web_facet(options = {})
300 name = options[:name] || "Web"
301 default_webroots = {}
302 buildr_project.assets.paths.each {|p| default_webroots[p] = "/" }
303 webroots = options[:webroots] || default_webroots
304 default_deployment_descriptors = []
305 ['web.xml', 'glassfish-web.xml', 'context.xml'].each do |descriptor|
306 webroots.each_pair do |path, relative_url|
307 next unless relative_url == "/"
308 d = "#{path}/WEB-INF/#{descriptor}"
309 default_deployment_descriptors << d if File.exist?(d)
310 end
311 end
312 deployment_descriptors = options[:deployment_descriptors] || default_deployment_descriptors
313
314 add_facet(name, "web") do |f|
315 f.configuration do |c|
316 c.descriptors do |d|
317 deployment_descriptors.each do |deployment_descriptor|
318 d.deploymentDescriptor :name => File.basename(deployment_descriptor), :url => file_path(deployment_descriptor)
319 end
320 end
321 c.webroots do |w|
322 webroots.each_pair do |webroot, relative_url|
323 w.root :url => file_path(webroot), :relative => relative_url
324 end
325 end
326 end
327 default_enable_jsf = webroots.select{|webroot| File.exist?("#{webroot}/WEB-INF/faces-config.xml")}
328 enable_jsf = options[:enable_jsf].nil? ? default_enable_jsf : options[:enable_jsf]
329 f.facet(:type => 'jsf', :name => 'JSF') do |jsf|
330 jsf.configuration
331 end if enable_jsf
332 end
333 end
334
335 def add_jruby_facet(options = {})
336 name = options[:name] || "JRuby"
337
338 ruby_version_file = buildr_project._('.ruby-version')
339 default_jruby_version = File.exist?(ruby_version_file) ? "rbenv: #{IO.read(ruby_version_file).strip}" : 'jruby-1.6.7.2'
340 jruby_version = options[:jruby_version] || default_jruby_version
341 add_facet(name, "JRUBY") do |f|
342 f.configuration do |c|
343 c.JRUBY_FACET_CONFIG_ID :NAME => "JRUBY_SDK_NAME", :VALUE => jruby_version
344 c.LOAD_PATH :number => "0"
345 c.I18N_FOLDERS :number => "0"
346 end
347 end
348 end
349
350 def add_jpa_facet(options = {})
351 name = options[:name] || "JPA"
352 factory_entry = options[:factory_entry] || buildr_project.name.to_s
353 validation_enabled = options[:validation_enabled].nil? ? true : options[:validation_enabled]
354 provider_enabled = options[:provider_enabled] || 'Hibernate'
355 default_persistence_xml = buildr_project._(:source, :main, :resources, "META-INF/persistence.xml")
356 persistence_xml = options[:persistence_xml] || default_persistence_xml
357 default_orm_xml = buildr_project._(:source, :main, :resources, "META-INF/orm.xml")
358 orm_xml = options[:orm_xml] || default_orm_xml
359 add_facet(name, "jpa") do |f|
360 f.configuration do |c|
361 c.setting :name => "validation-enabled", :value => validation_enabled
362 c.setting :name => "provider-name", :value => provider_enabled
363 c.tag!('datasource-mapping') do |ds|
364 ds.tag!('factory-entry', :name => factory_entry)
365 end
366 if File.exist?(persistence_xml) || default_persistence_xml != persistence_xml
367 c.deploymentDescriptor :name => 'persistence.xml', :url => file_path(persistence_xml)
368 end
369 if File.exist?(orm_xml) || default_orm_xml != orm_xml
370 c.deploymentDescriptor :name => 'orm.xml', :url => file_path(orm_xml)
371 end
372 end
373 end
374 end
375
376 def add_ejb_facet(options = {})
377 name = options[:name] || "EJB"
378 default_ejb_xml = buildr_project._(:source, :main, :resources, "WEB-INF/ejb-jar.xml")
379 ejb_xml = options[:ejb_xml] || default_ejb_xml
380 ejb_roots = options[:ejb_roots] || [buildr_project.compile.sources, buildr_project.resources.sources].flatten
381
382 add_facet(name, "ejb") do |facet|
383 facet.configuration do |c|
384 c.descriptors do |d|
385 if File.exist?(ejb_xml) || default_ejb_xml != ejb_xml
386 d.deploymentDescriptor :name => 'ejb-jar.xml', :url => file_path(ejb_xml)
387 end
388 end
389 c.ejbRoots do |e|
390 ejb_roots.each do |ejb_root|
391 e.root :url => file_path(ejb_root)
392 end
393 end
394 end
395 end
396 end
397
398 protected
399
400 def test_dependency_details
401 main_dependencies_paths = main_dependencies.map(&:to_s)
402 target_dir = buildr_project.compile.target.to_s
403 test_dependencies.select { |d| d.to_s != target_dir }.collect do |d|
404 dependency_path = d.to_s
405 export = main_dependencies_paths.include?(dependency_path)
406 source_path = nil
407 if d.respond_to?(:to_spec_hash)
408 source_spec = d.to_spec_hash.merge(:classifier => 'sources')
409 source_path = Buildr.artifact(source_spec).to_s
410 source_path = nil unless File.exist?(source_path)
411 end
412 [dependency_path, export, source_path]
413 end
414 end
415
416 def base_document
417 target = StringIO.new
418 Builder::XmlMarkup.new(:target => target).module(:version => "4", :relativePaths => "true", :type => self.type)
419 Buildr::IntellijIdea.new_document(target.string)
420 end
421
422 def initial_components
423 []
424 end
425
426 def default_components
427 [
428 lambda { module_root_component },
429 lambda { facet_component }
430 ]
431 end
432
433 def facet_component
434 create_composite_component("FacetManager", self.facets)
435 end
436
437 def module_root_component
438 create_component("NewModuleRootManager", "inherit-compiler-output" => "false") do |xml|
439 generate_compile_output(xml)
440 generate_content(xml) unless skip_content?
441 generate_initial_order_entries(xml)
442 project_dependencies = []
443
444
445 self.test_dependency_details.each do |dependency_path, export, source_path|
446 next unless export
447 generate_lib(xml, dependency_path, export, source_path, project_dependencies)
448 end
449
450 self.test_dependency_details.each do |dependency_path, export, source_path|
451 next if export
452 generate_lib(xml, dependency_path, export, source_path, project_dependencies)
453 end
454
455 xml.orderEntryProperties
456 end
457 end
458
459 def generate_lib(xml, dependency_path, export, source_path, project_dependencies)
460 project_for_dependency = Buildr.projects.detect do |project|
461 [project.packages, project.compile.target, project.resources.target, project.test.compile.target, project.test.resources.target].flatten.
462 detect { |artifact| artifact.to_s == dependency_path }
463 end
464 if project_for_dependency
465 if project_for_dependency.iml? &&
466 !project_dependencies.include?(project_for_dependency) &&
467 project_for_dependency != self.buildr_project
468 generate_project_dependency(xml, project_for_dependency.iml.name, export, !export)
469 end
470 project_dependencies << project_for_dependency
471 else
472 generate_module_lib(xml, url_for_path(dependency_path), export, (source_path ? url_for_path(source_path) : nil), !export)
473 end
474 end
475
476 def jar_path(path)
477 "jar://#{resolve_path(path)}!/"
478 end
479
480 def url_for_path(path)
481 if path =~ /jar$/i
482 jar_path(path)
483 else
484 file_path(path)
485 end
486 end
487
488 def resolve_path(path)
489 resolve_path_from_base(path, "$MODULE_DIR$")
490 end
491
492 def generate_compile_output(xml)
493 xml.output(:url => file_path(self.main_output_dir.to_s))
494 xml.tag!("output-test", :url => file_path(self.test_output_dir.to_s))
495 xml.tag!("exclude-output")
496 end
497
498 def generate_content(xml)
499 xml.content(:url => "file://$MODULE_DIR$") do
500 # Source folders
501 {
502 :main => self.main_source_directories,
503 :test => self.test_source_directories
504 }.each do |kind, directories|
505 directories.map { |dir| dir.to_s }.compact.sort.uniq.each do |dir|
506 xml.sourceFolder :url => file_path(dir), :isTestSource => (kind == :test ? 'true' : 'false')
507 end
508 end
509
510 # Exclude target directories
511 self.net_excluded_directories.
512 collect { |dir| file_path(dir) }.
513 select { |dir| relative_dir_inside_dir?(dir) }.
514 sort.each do |dir|
515 xml.excludeFolder :url => dir
516 end
517 end
518 end
519
520 def relative_dir_inside_dir?(dir)
521 !dir.include?("../")
522 end
523
524 def generate_initial_order_entries(xml)
525 xml.orderEntry :type => "sourceFolder", :forTests => "false"
526 xml.orderEntry :type => "jdk", :jdkName => jdk_version, :jdkType => "JavaSDK"
527 end
528
529 def generate_project_dependency(xml, other_project, export, test = false)
530 attribs = {:type => 'module', "module-name" => other_project}
531 attribs[:exported] = '' if export
532 attribs[:scope] = 'TEST' if test
533 xml.orderEntry attribs
534 end
535
536 def generate_module_lib(xml, path, export, source_path, test = false)
537 attribs = {:type => 'module-library'}
538 attribs[:exported] = '' if export
539 attribs[:scope] = 'TEST' if test
540 xml.orderEntry attribs do
541 xml.library do
542 xml.CLASSES do
543 xml.root :url => path
544 end
545 xml.JAVADOC
546 xml.SOURCES do
547 if source_path
548 xml.root :url => source_path
549 end
550 end
551 end
552 end
553 end
554
555 # Don't exclude things that are subdirectories of other excluded things
556 def net_excluded_directories
557 net = []
558 all = self.excluded_directories.map { |dir| buildr_project._(dir.to_s) }.sort_by { |d| d.size }
559 all.each_with_index do |dir, i|
560 unless all[0 ... i].find { |other| dir =~ /^#{other}/ }
561 net << dir
562 end
563 end
564 net
565 end
566 end
567
568 # IdeaModule represents an .ipr file
569 class IdeaProject < IdeaFile
570 attr_accessor :extra_modules
571 attr_accessor :artifacts
572 attr_accessor :configurations
573 attr_writer :jdk_version
574
575 def initialize(buildr_project)
576 super()
577 @buildr_project = buildr_project
578 @extra_modules = []
579 @artifacts = []
580 @configurations = []
581 end
582
583 def jdk_version
584 @jdk_version ||= buildr_project.compile.options.source || "1.6"
585 end
586
587 def add_artifact(name, type, build_on_make = false)
588 add_to_composite_component(self.artifacts) do |xml|
589 xml.artifact(:name => name, :type => type, :"build-on-make" => build_on_make) do |xml|
590 yield xml if block_given?
591 end
592 end
593 end
594
595 def add_configuration(name, type, factory_name, default = false)
596 add_to_composite_component(self.configurations) do |xml|
597 xml.configuration(:name => name, :type => type, :factoryName => factory_name, :default => default) do |xml|
598 yield xml if block_given?
599 end
600 end
601 end
602
603 def add_exploded_war_artifact(project, options = {})
604 artifact_name = options[:name] || project.iml.id
605 build_on_make = options[:build_on_make].nil? ? false : options[:build_on_make]
606
607 add_artifact(artifact_name, "exploded-war", build_on_make) do |xml|
608 dependencies = (options[:dependencies] || ([project] + project.compile.dependencies)).flatten
609 libraries, projects = partition_dependencies(dependencies)
610
611 ## The content here can not be indented
612 output_dir = options[:output_dir] || project._(:artifacts, artifact_name)
613 xml.tag!('output-path', output_dir)
614
615 xml.root :id => "root" do
616 xml.element :id => "directory", :name => "WEB-INF" do
617 xml.element :id => "directory", :name => "classes" do
618 projects.each do |p|
619 xml.element :id => "module-output", :name => p.iml.id
620 end
621 if options[:enable_jpa]
622 module_names = options[:jpa_module_names] || [project.iml.id]
623 module_names.each do |module_name|
624 facet_name = options[:jpa_facet_name] || "JPA"
625 xml.element :id => "jpa-descriptors", :facet => "#{module_name}/jpa/#{facet_name}"
626 end
627 end
628 if options[:enable_ejb]
629 module_names = options[:ejb_module_names] || [project.iml.id]
630 module_names.each do |module_name|
631 facet_name = options[:ejb_facet_name] || "EJB"
632 xml.element :id => "javaee-facet-resources", :facet => "#{module_name}/ejb/#{facet_name}"
633 end
634 end
635 end
636 xml.element :id => "directory", :name => "lib" do
637 libraries.each(&:invoke).map(&:to_s).each do |dependency_path|
638 xml.element :id => "file-copy", :path => resolve_path(dependency_path)
639 end
640 end
641 end
642
643 if options[:enable_war].nil? || options[:enable_war]
644 module_names = options[:war_module_names] || [project.iml.id]
645 module_names.each do |module_name|
646 facet_name = options[:war_facet_name] || "Web"
647 xml.element :id => "javaee-facet-resources", :facet => "#{module_name}/web/#{facet_name}"
648 end
649 end
650
651 if options[:enable_gwt]
652 module_names = options[:gwt_module_names] || [project.iml.id]
653 module_names.each do |module_name|
654 facet_name = options[:gwt_facet_name] || "GWT"
655 xml.element :id => "gwt-compiler-output", :facet => "#{module_name}/gwt/#{facet_name}"
656 end
657 end
658 end
659 end
660 end
661
662 def add_exploded_ear_artifact(project, options ={})
663
664 artifact_name = options[:name] || project.iml.id
665 build_on_make = options[:build_on_make].nil? ? true : options[:build_on_make]
666
667 add_artifact(artifact_name, "exploded-ear", build_on_make) do |xml|
668 dependencies = (options[:dependencies] || ([project] + project.compile.dependencies)).flatten
669 libraries, projects = partition_dependencies(dependencies)
670
671 ## The content here can not be indented
672 output_dir = options[:output_dir] || project._(:artifacts, artifact_name)
673 xml.tag!('output-path', output_dir)
674
675 xml.root :id => "root" do
676
677 xml.element :id => "module-output", :name => project.iml.id
678
679 projects.each do |p|
680 xml.element :id => "directory", :name => p.iml.id do
681 xml.element :id => "module-output", :name => p.iml.id
682 end
683 end
684
685 xml.element :id => "directory", :name => "lib" do
686 libraries.each(&:invoke).map(&:to_s).each do |dependency_path|
687 xml.element :id => "file-copy", :path => resolve_path(dependency_path)
688 end
689 end
690
691 end
692 end
693 end
694
695 def add_exploded_ejb_artifact(project, options = {})
696
697 artifact_name = options[:name] || project.iml.id
698 build_on_make = options[:build_on_make].nil? ? true : options[:build_on_make]
699
700 add_artifact(artifact_name, "exploded-ejb", build_on_make) do |xml|
701 dependencies = (options[:dependencies] || ([project] + project.compile.dependencies)).flatten
702 libraries, projects = partition_dependencies(dependencies)
703
704 ## The content here can not be indented
705 output_dir = options[:output_dir] || project._(:artifacts, artifact_name)
706 xml.tag!('output-path', output_dir)
707
708 xml.root :id => "root" do
709
710 xml.element :id => "module-output", :name => project.iml.id
711
712 if options[:enable_jpa]
713 module_names = options[:jpa_module_names] || [project.iml.id]
714 module_names.each do |module_name|
715 facet_name = options[:jpa_facet_name] || "JPA"
716 xml.element :id => "jpa-descriptors", :facet => "#{module_name}/jpa/#{facet_name}"
717 end
718 end
719
720 if options[:enable_ejb].nil? || options[:enable_ejb]
721 module_names = options[:ejb_module_names] || [project.iml.id]
722 module_names.each do |module_name|
723 facet_name = options[:ejb_facet_name] || "EJB"
724 xml.element :id => "javaee-facet-resources", :facet => "#{module_name}/ejb/#{facet_name}"
725 end
726 end
727
728 end
729 end
730 end
731
732
733 def add_gwt_configuration(launch_page, project, options = {})
734 name = options[:name] || "Run #{launch_page}"
735 shell_parameters = options[:shell_parameters] || ""
736 vm_parameters = options[:vm_parameters] || "-Xmx512m"
737
738 add_configuration(name, "GWT.ConfigurationType", "GWT Configuration") do |xml|
739 xml.module(:name => project.iml.id)
740 xml.option(:name => "RUN_PAGE", :value => launch_page)
741 xml.option(:name => "SHELL_PARAMETERS", :value => shell_parameters)
742 xml.option(:name => "VM_PARAMETERS", :value => vm_parameters)
743
744 xml.RunnerSettings(:RunnerId => "Run")
745 xml.ConfigurationWrapper(:RunnerId => "Run")
746 xml.method()
747 end
748 end
749
750 protected
751
752 def extension
753 "ipr"
754 end
755
756 def base_document
757 target = StringIO.new
758 Builder::XmlMarkup.new(:target => target).project(:version => "4")
759 Buildr::IntellijIdea.new_document(target.string)
760 end
761
762 def default_components
763 [
764 lambda { modules_component },
765 vcs_component,
766 artifacts_component,
767 configurations_component,
768 lambda { framework_detection_exclusion_component }
769 ]
770 end
771
772 def framework_detection_exclusion_component
773 create_component('FrameworkDetectionExcludesConfiguration') do |xml|
774 xml.file :url => file_path(buildr_project._(:artifacts))
775 end
776 end
777
778 def initial_components
779 [
780 lambda { project_root_manager_component },
781 lambda { project_details_component }
782 ]
783 end
784
785 def project_root_manager_component
786 attribs = {}
787 attribs["version"] = "2"
788 attribs["languageLevel"] = "JDK_#{self.jdk_version.gsub('.', '_')}"
789 attribs["assert-keyword"] = "true"
790 attribs["jdk-15"] = (jdk_version >= "1.5").to_s
791 attribs["project-jdk-name"] = self.jdk_version
792 attribs["project-jdk-type"] = "JavaSDK"
793 create_component("ProjectRootManager", attribs) do |xml|
794 xml.output("url" => file_path(buildr_project._(:target, :idea, :project_out)))
795 end
796 end
797
798 def project_details_component
799 create_component("ProjectDetails") do |xml|
800 xml.option("name" => "projectName", "value" => self.name)
801 end
802 end
803
804 def modules_component
805 create_component("ProjectModuleManager") do |xml|
806 xml.modules do
807 buildr_project.projects.select { |subp| subp.iml? }.each do |subproject|
808 module_path = subproject.base_dir.gsub(/^#{buildr_project.base_dir}\//, '')
809 path = "#{module_path}/#{subproject.iml.name}.iml"
810 attribs = {:fileurl => "file://$PROJECT_DIR$/#{path}", :filepath => "$PROJECT_DIR$/#{path}"}
811 if subproject.iml.group == true
812 attribs[:group] = subproject.parent.name.gsub(':', '/')
813 elsif !subproject.iml.group.nil?
814 attribs[:group] = subproject.iml.group.to_s
815 end
816 xml.module attribs
817 end
818 self.extra_modules.each do |iml_file|
819 xml.module :fileurl => "file://$PROJECT_DIR$/#{iml_file}",
820 :filepath => "$PROJECT_DIR$/#{iml_file}"
821 end
822 if buildr_project.iml?
823 xml.module :fileurl => "file://$PROJECT_DIR$/#{buildr_project.iml.name}.iml",
824 :filepath => "$PROJECT_DIR$/#{buildr_project.iml.name}.iml"
825 end
826 end
827 end
828 end
829
830 def vcs_component
831 project_directories = buildr_project.projects.select { |p| p.iml? }.collect { |p| p.base_dir }
832 project_directories << buildr_project.base_dir
833 # Guess the iml file is in the same dir as base dir
834 project_directories += self.extra_modules.collect { |p| File.dirname(p) }
835
836 project_directories = project_directories.sort.uniq
837
838 mappings = {}
839
840 project_directories.each do |dir|
841 if File.directory?("#{dir}/.git")
842 mappings[dir] = "Git"
843 elsif File.directory?("#{dir}/.svn")
844 mappings[dir] = "svn"
845 end
846 end
847
848 if mappings.size > 1
849 create_component("VcsDirectoryMappings") do |xml|
850 mappings.each_pair do |dir, vcs_type|
851 resolved_dir = resolve_path(dir)
852 mapped_dir = resolved_dir == '$PROJECT_DIR$/.' ? buildr_project.base_dir : resolved_dir
853 xml.mapping :directory => mapped_dir, :vcs => vcs_type
854 end
855 end
856 end
857 end
858
859 def artifacts_component
860 create_composite_component("ArtifactManager", self.artifacts)
861 end
862
863 def configurations_component
864 create_composite_component("ProjectRunConfigurationManager", self.configurations)
865 end
866
867 def resolve_path(path)
868 resolve_path_from_base(path, "$PROJECT_DIR$")
869 end
870
871 private
872
873 def partition_dependencies(dependencies)
874 libraries = []
875 projects = []
876
877 dependencies.each do |dependency|
878 artifacts = Buildr.artifacts(dependency)
879 artifacts_as_strings = artifacts.map(&:to_s)
880 project = Buildr.projects.detect do |project|
881 [project.packages, project.compile.target, project.resources.target, project.test.compile.target, project.test.resources.target].flatten.
882 detect { |component| artifacts_as_strings.include?(component.to_s) }
883 end
884 if project
885 projects << project
886 else
887 libraries += artifacts
888 end
889 end
890 return libraries.uniq, projects.uniq
891 end
892 end
893
894 module ProjectExtension
895 include Extension
896
897 first_time do
898 desc "Generate Intellij IDEA artifacts for all projects"
899 Project.local_task "idea" => "artifacts"
900
901 desc "Delete the generated Intellij IDEA artifacts"
902 Project.local_task "idea:clean"
903 end
904
905 before_define do |project|
906 project.recursive_task("idea")
907 project.recursive_task("idea:clean")
908 end
909
910 after_define do |project|
911 idea = project.task("idea")
912
913 files = [
914 (project.iml if project.iml?),
915 (project.ipr if project.ipr?)
916 ].compact
917
918 files.each do |ideafile|
919 module_dir = File.dirname(ideafile.filename)
920 idea.enhance do |task|
921 mkdir_p module_dir
922 info "Writing #{ideafile.filename}"
923 t = Tempfile.open("buildr-idea")
924 temp_filename = t.path
925 t.close!
926 File.open(temp_filename, "w") do |f|
927 ideafile.write f
928 end
929 mv temp_filename, ideafile.filename
930 end
931 end
932
933 project.task("idea:clean") do
934 files.each do |f|
935 info "Removing #{f.filename}" if File.exist?(f.filename)
936 rm_rf f.filename
937 end
938 end
939 end
940
941 def ipr
942 if ipr?
943 @ipr ||= IdeaProject.new(self)
944 else
945 raise "Only the root project has an IPR"
946 end
947 end
948
949 def ipr?
950 !@no_ipr && self.parent.nil?
951 end
952
953 def iml
954 if iml?
955 unless @iml
956 inheritable_iml_source = self.parent
957 while inheritable_iml_source && !inheritable_iml_source.iml?
958 inheritable_iml_source = inheritable_iml_source.parent;
959 end
960 @iml = inheritable_iml_source ? inheritable_iml_source.iml.clone : IdeaModule.new
961 @iml.buildr_project = self
962 end
963 return @iml
964 else
965 raise "IML generation is disabled for #{self.name}"
966 end
967 end
968
969 def no_ipr
970 @no_ipr = true
971 end
972
973 def no_iml
974 @has_iml = false
975 end
976
977 def iml?
978 @has_iml = @has_iml.nil? ? true : @has_iml
979 end
980 end
981 end
982 end
983
984 class Buildr::Project
985 include Buildr::IntellijIdea::ProjectExtension
986 end