test: hard-coded debug port dropped to avoid conflicts
[idea/community.git] / build / scripts / utils.gant
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 import org.jetbrains.jps.gant.JpsGantTool
18 import org.jetbrains.jps.gant.TeamCityBuildInfoPrinter
19 import org.jetbrains.jps.model.java.JavaSourceRootType
20 import org.jetbrains.jps.model.java.JpsJavaExtensionService
21 import org.jetbrains.jps.model.module.JpsModule
22
23 includeTool << JpsGantTool
24
25 binding.setVariable("p", {String key, String defaultValue = null ->
26   try {
27     return getProperty(key) as String
28   }
29   catch (MissingPropertyException e) {
30     if (defaultValue != null) {
31       return defaultValue
32     }
33     throw e;
34   }
35 })
36
37 binding.setVariable("guessJdk", {
38   String javaHome = p("java.home")
39
40   if (new File(javaHome).getName() == "jre") {
41     javaHome = new File(javaHome).getParent()
42   }
43
44   return javaHome
45 })
46
47 binding.setVariable("includeFile", {String filePath ->
48   Script s = groovyShell.parse(new File(filePath))
49   s.setBinding(binding)
50   s
51 })
52
53 binding.setVariable("isMac", {
54   return System.getProperty("os.name").toLowerCase().startsWith("mac")
55 })
56
57 binding.setVariable("isWin", {
58   return System.getProperty("os.name").toLowerCase().startsWith("windows")
59 })
60
61 binding.setVariable("isEap", {
62   return "true" == p("component.version.eap")
63 })
64
65 binding.setVariable("mem32", "-server -Xms128m -Xmx512m -XX:MaxPermSize=250m -XX:ReservedCodeCacheSize=150m")
66 binding.setVariable("mem64", "-Xms128m -Xmx750m -XX:MaxPermSize=350m -XX:ReservedCodeCacheSize=225m")
67 binding.setVariable("common_vmoptions", "-ea -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true " +
68                                         "-Djsse.enableSNIExtension=false " +
69                                         "-XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=50")
70
71 binding.setVariable("vmOptions", { "$common_vmoptions ${isEap() ? '-XX:+HeapDumpOnOutOfMemoryError' : ''}".trim() })
72 binding.setVariable("vmOptions32", { "$mem32 ${vmOptions()}".trim() })
73 binding.setVariable("vmOptions64", { "$mem64 ${vmOptions()}".trim() })
74
75 binding.setVariable("yjpOptions", { String systemSelector, String platformSuffix = "" ->
76   "-agentlib:yjpagent$platformSuffix=probe_disable=*,disablealloc,disabletracing,onlylocal,disableexceptiontelemetry,delay=10000,sessionname=$systemSelector".trim()
77 })
78 binding.setVariable("vmOptions32yjp", { String systemSelector ->
79   "${vmOptions32()} ${yjpOptions(systemSelector)}".trim()
80 })
81 binding.setVariable("vmOptions64yjp", { String systemSelector ->
82   "${vmOptions64()} ${yjpOptions(systemSelector, "64")}".trim()
83 })
84
85 binding.setVariable("isDefined", {String key ->
86   try {
87     this[key]
88     return true
89   }
90   catch (MissingPropertyException ignored) {
91     return false
92   }
93 })
94
95 private String require(String key) {
96   try {
97     this[key]
98   }
99   catch (MissingPropertyException ignored) {
100     projectBuilder.error("Property '$key' is required")
101   }
102 }
103
104 private String require(String key, String defaultValue) {
105   try {
106     this[key]
107   }
108   catch (MissingPropertyException ignored) {
109     projectBuilder.info("'$key' is not defined. Defaulting to '$defaultValue'")
110     this[key] = defaultValue
111   }
112 }
113
114 binding.setVariable("requireProperty", {String key, String defaultValue = null ->
115   if (defaultValue == null) {
116     require(key)
117   }
118   else {
119     require(key, defaultValue)
120   }
121 })
122
123 binding.setVariable("guessHome", {
124   // current file is supposed to be at build/scripts/*.gant path
125   String uri = requireProperty("gant.file")
126   new File(new URI(uri).getSchemeSpecificPart()).getParentFile().getParentFile().getParent()
127 })
128
129 binding.setVariable("loadProject", {
130   requireProperty("jdkHome", guessJdk())
131   def mac = isMac()
132   jdk("IDEA jdk", jdkHome) {
133     if (!mac) {
134       classpath "$jdkHome/lib/tools.jar"
135     }
136   }
137   projectBuilder.buildIncrementally = Boolean.parseBoolean(p("jps.build.incrementally", "false"))
138   def dataDirName = projectBuilder.buildIncrementally ? ".jps-incremental-build" : ".jps-build-data"
139   projectBuilder.dataStorageRoot = new File("$home/$dataDirName")
140   projectBuilder.setupAdditionalLogging(new File("${p("teamcity.build.tempDir", p("java.io.tmpdir"))}/system/build-log/build.log"),
141                                         p("jps.build.debug.logging.categories", ""))
142   loadProjectFromPath(home)
143
144   def compilerOptions = JpsJavaExtensionService.instance.getOrCreateCompilerConfiguration(project).currentCompilerOptions
145   compilerOptions.GENERATE_NO_WARNINGS = true
146   compilerOptions.DEPRECATION = false
147   compilerOptions.ADDITIONAL_OPTIONS_STRING = compilerOptions.ADDITIONAL_OPTIONS_STRING.replace("-Xlint:unchecked", "")
148 })
149
150 boolean hasSourceRoots(JpsModule module) {
151   return module.getSourceRoots(JavaSourceRootType.SOURCE).iterator().hasNext()
152 }
153
154 binding.setVariable("findModule", {String name ->
155   project.modules.find { it.name == name }
156 })
157
158 binding.setVariable("allModules", {
159   return project.modules
160 })
161
162 binding.setVariable("printUnusedModules", {Set<String> usedModules ->
163   allModules().each {JpsModule m ->
164     if (!usedModules.contains(m.name) && hasSourceRoots(m)) {
165       projectBuilder.warning("Module $m.name is not used in project layout")
166     }
167   }
168 })
169
170 requireProperty("home", guessHome())
171
172 String readSnapshotBuild() {
173   def file = new File("$home/community/build.txt")
174   if (!file.exists()) {
175     file = new File("$home/build.txt")
176   }
177
178   return file.readLines().get(0)
179 }
180
181 binding.setVariable("snapshot", readSnapshotBuild())
182
183 projectBuilder.buildInfoPrinter = new TeamCityBuildInfoPrinter()
184 projectBuilder.compressJars = false
185
186 binding.setVariable("notifyArtifactBuilt", { String artifactPath ->
187   if (!artifactPath.startsWith(home)) {
188     projectBuilder.error("Artifact path $artifactPath should start with $home")
189   }
190   def relativePath = artifactPath.substring(home.length())
191   if (relativePath.startsWith("/")) {
192     relativePath = relativePath.substring(1)
193   }
194   def file = new File(artifactPath)
195   if (file.isDirectory()) {
196     relativePath += "=>" + file.name
197   }
198   projectBuilder.info("##teamcity[publishArtifacts '$relativePath']")
199 })
200
201 def debugPort = System.getProperty("debug.port")
202 def debugSuspend = System.getProperty("debug.suspend") ?: "n"
203 if (debugSuspend == 'y') {
204   println """
205
206 ------------->------------- The process suspended until remote debugger connects to debug port -------------<-------------
207 ---------------------------------------^------^------^------^------^------^------^----------------------------------------
208
209 """
210 }
211
212 binding.setVariable("patchFiles", { List files, Map args, String marker = "__" ->
213   files.each { file ->
214     args.each { arg ->
215       ant.replace(file: file, token: "${marker}${arg.key}${marker}", value:  arg.value)
216     }
217   }
218 })
219
220 binding.setVariable("copyAndPatchFile", { String file, String target, Map args, String marker = "__" ->
221   ant.copy(file: file, tofile: target, overwrite: "true") {
222     filterset(begintoken: marker, endtoken: marker) {
223       args.each {
224         filter(token: it.key, value: it.value)
225       }
226     }
227   }
228 })
229
230 binding.setVariable("copyAndPatchFiles", { Closure files, String target, Map args, String marker = "__" ->
231   ant.copy(todir: target, overwrite: "true") {
232     files()
233
234     filterset(begintoken: marker, endtoken: marker) {
235       args.each {
236         filter(token: it.key, value: it.value)
237       }
238     }
239   }
240 })
241
242 binding.setVariable("wireBuildDate", { String buildNumber, String appInfoFile ->
243   ant.tstamp()
244   patchFiles([appInfoFile], ["BUILD_NUMBER": buildNumber, "BUILD_DATE": DSTAMP])
245 })
246
247 binding.setVariable("commonJvmArgs", {
248   def jdwp = "-Xrunjdwp:transport=dt_socket,server=y,suspend=$debugSuspend"
249   if (debugPort != null) jdwp += ",address=$debugPort"
250   return [
251     "-ea",
252     "-server",
253     "-Xbootclasspath/p:${projectBuilder.moduleOutput(findModule("boot"))}",
254     "-XX:+HeapDumpOnOutOfMemoryError",
255     "-Didea.home.path=$home",
256     "-Didea.config.path=${p("teamcity.build.tempDir")}/config",
257     "-Didea.system.path=${p("teamcity.build.tempDir")}/system",
258     "-Xdebug",
259     jdwp
260   ]
261 })
262
263 binding.setVariable("classPathLibs", [
264         "bootstrap.jar",
265         "extensions.jar",
266         "util.jar",
267         "jdom.jar",
268         "log4j.jar",
269         "trove4j.jar",
270         "jna.jar"
271 ])
272
273 binding.setVariable("platformApiModules", [
274   "analysis-api",
275   "core-api",
276   "dvcs-api",
277   "editor-ui-api",
278   "external-system-api",
279   "indexing-api",
280   "jps-model-api",
281   "lang-api",
282   "lvcs-api",
283   "projectModel-api",
284   "platform-api",
285   "structure-view-api",
286   "usageView",
287   "vcs-api",
288   "vcs-api-core",
289   "vcs-log-api",
290   "vcs-log-graph-api",
291   "xdebugger-api",
292   "remote-servers-api",
293   "remote-servers-agent-rt",
294   "xml-analysis-api",
295   "xml-openapi",
296   "xml-psi-api",
297   "xml-structure-view-api",
298 ])
299
300
301 binding.setVariable("platformImplementationModules", [
302   "analysis-impl",
303   "core-impl",
304   "dvcs-impl",
305   "editor-ui-ex",
306   "images",
307   "indexing-impl",
308   "jps-model-impl",
309   "jps-model-serialization",
310   "lang-impl",
311   "lvcs-impl",
312   "projectModel-impl",
313   "platform-impl",
314   "structure-view-impl",
315   "vcs-impl",
316   "vcs-log-graph",
317   "vcs-log-impl",
318   "testRunner",
319   "smRunner",
320   "relaxng",
321   "RegExpSupport",
322   "spellchecker",
323   "xdebugger-impl",
324   "remote-servers-impl",
325   "xml",
326   "xml-analysis-impl",
327   "xml-psi-impl",
328   "xml-structure-view-impl",
329   "json",
330   "protocol-reader-runtime",
331   "script-debugger-backend",
332   "script-debugger-ui"
333 ])
334
335 binding.setVariable("layoutMacApp", { String path, String ch, Map args ->
336   ant.copy(todir: "$path/bin") {
337     fileset(dir: "$ch/bin/mac")
338   }
339
340   ant.copy(todir: path) {
341     fileset(dir: "$ch/build/conf/mac/Contents")
342   }
343
344   ant.tstamp() {
345     format(property: "todayYear", pattern: "yyyy")
346   }
347
348   String executable = args.executable != null ? args.executable : p("component.names.product").toLowerCase()
349   String helpId = args.help_id != null ? args.help_id : "IJ"
350   String icns = "idea.icns"
351   String helpIcns = "$path/Resources/${helpId}.help/Contents/Resources/Shared/product.icns"
352   if (args.icns != null) {
353     ant.delete(file: "$path/Resources/idea.icns")
354     ant.copy(file: args.icns, todir: "$path/Resources")
355     ant.copy(file: args.icns, tofile: helpIcns)
356     icns = new File((String)args.icns).getName();
357   } else {
358     ant.copy(file: "$path/Resources/idea.icns", tofile: helpIcns)
359   }
360
361   String fullName = args.fullName != null ? args.fullName : p("component.names.fullname")
362
363   String vmOptions = "-Dfile.encoding=UTF-8 ${vmOptions()} -Xverify:none"
364
365   String minor = p("component.version.minor")
366   String version = isEap() && !minor.contains("RC") && !minor.contains("Beta") ? "EAP $args.buildNumber" : "${p("component.version.major")}.${minor}"
367
368   Map properties = readIdeaProperties(args)
369
370   def coreKeys = ["idea.platform.prefix", "idea.paths.selector"]
371
372   String coreProperties = submapToXml(properties, coreKeys);
373
374   StringBuilder effectiveProperties = new StringBuilder()
375   properties.each { k, v ->
376     if (!coreKeys.contains(k)) {
377       effectiveProperties.append("$k=$v\n");
378     }
379   }
380
381   new File("$path/bin/idea.properties").text = effectiveProperties.toString()
382   String ideaVmOptions = "$mem64 -XX:+UseCompressedOops".split(" ").join("\n")
383   if (isEap() && !args.mac_no_yjp) {
384     ideaVmOptions += " ${yjpOptions(args.system_selector)}"
385   }
386   new File("$path/bin/idea.vmoptions").text = ideaVmOptions
387
388   String classPath = classPathLibs.collect {"\$APP_PACKAGE/Contents/lib/${it}" }.join(":")
389
390   String archs = """
391     <key>LSArchitecturePriority</key>
392     <array>"""
393   (args.archs != null ? args.archs : ["x86_64", "i386"]).each {
394     archs += "<string>${it}</string>"
395   }
396   archs +="</array>\n"
397   
398   String urlSchemes = ""
399   if (args.urlSchemes != null) {
400     urlSchemes += """
401       <key>CFBundleURLTypes</key>
402       <array>
403         <dict>
404           <key>CFBundleTypeRole</key>
405           <string>Editor</string>
406           <key>CFBundleURLName</key>
407           <string>Stacktrace</string>
408           <key>CFBundleURLSchemes</key>
409           <array>
410 """
411     args.urlSchemes.each { scheme ->
412       urlSchemes += "            <string>${scheme}</string>"
413     }
414     urlSchemes += """
415           </array>
416         </dict>
417       </array>
418 """
419   }
420
421   ant.replace(file: "$path/Info.plist") {
422     replacefilter(token: "@@build@@", value: args.buildNumber)
423     replacefilter(token: "@@doc_types@@", value: ifNull(args.doc_types, ""))
424     replacefilter(token: "@@executable@@", value: executable)
425     replacefilter(token: "@@icns@@", value: icns)
426     replacefilter(token: "@@bundle_name@@", value: fullName)
427     replacefilter(token: "@@bundle_identifier@@", value: args.bundleIdentifier)
428     replacefilter(token: "@@year@@", value: "$todayYear")
429     replacefilter(token: "@@version@@", value: version)
430     replacefilter(token: "@@vmoptions@@", value: vmOptions)
431     replacefilter(token: "@@idea_properties@@", value: coreProperties)
432     replacefilter(token: "@@class_path@@", value: classPath)
433     replacefilter(token: "@@help_id@@", value: helpId)
434     replacefilter(token: "@@url_schemes@@", value: urlSchemes)
435     replacefilter(token: "@@archs@@", value: archs)
436     replacefilter(token: "@@min_osx@@", value: ifNull(args.min_osx, "10.6"))
437   }
438
439   if (executable != "idea") {
440     ant.move(file: "$path/MacOS/idea", tofile: "$path/MacOS/$executable")
441   }
442
443   ant.replace(file: "$path/bin/inspect.sh") {
444     replacefilter(token: "@@product_full@@", value: fullName)
445     replacefilter(token: "@@script_name@@", value: executable)
446   }
447   if (args.inspect_script != null && args.inspect_script != "inspect") {
448     ant.move(file: "$path/bin/inspect.sh", tofile: "$path/bin/${args.inspect_script}.sh")
449   }
450
451   ant.fixcrlf(srcdir: "$path/bin", includes: "*.sh", eol: "unix")
452 })
453
454 binding.setVariable("winScripts", { String target, String home, String name, Map args ->
455   String fullName = args.fullName != null ? args.fullName : p("component.names.fullname")
456   String product_uc = args.product_uc != null ? args.product_uc : p("component.names.product").toUpperCase()
457   String vm_options = args.vm_options != null ? args.vm_options : "${p("component.names.product").toLowerCase()}.exe"
458   if (vm_options.endsWith(".exe")) {
459     vm_options = vm_options.replace(".exe", "%BITS%.exe")
460   }
461   else {
462     vm_options = vm_options + "%BITS%"
463   }
464
465   String classPath = "SET CLASS_PATH=%IDE_HOME%\\lib\\${classPathLibs[0]}\n"
466   classPath += classPathLibs[1..-1].collect {"SET CLASS_PATH=%CLASS_PATH%;%IDE_HOME%\\lib\\${it}"}.join("\n")
467   if (args.tools_jar) classPath += "\nSET CLASS_PATH=%CLASS_PATH%;%JDK%\\lib\\tools.jar"
468
469   ant.copy(todir: "$target/bin") {
470     fileset(dir: "$home/bin/scripts/win")
471
472     filterset(begintoken: "@@", endtoken: "@@") {
473       filter(token: "product_full", value: fullName)
474       filter(token: "product_uc", value: product_uc)
475       filter(token: "vm_options", value: vm_options)
476       filter(token: "isEap", value: isEap())
477       filter(token: "system_selector", value: args.system_selector)
478       filter(token: "ide_jvm_args", value: ifNull(args.ide_jvm_args, ""))
479       filter(token: "class_path", value: classPath)
480       filter(token: "script_name", value: name)
481     }
482   }
483
484   if (name != "idea.bat") {
485     ant.move(file: "$target/bin/idea.bat", tofile: "$target/bin/$name")
486   }
487   if (args.inspect_script != null && args.inspect_script != "inspect") {
488     ant.move(file: "$target/bin/inspect.bat", tofile: "$target/bin/${args.inspect_script}.bat")
489   }
490
491   ant.fixcrlf(srcdir: "$target/bin", includes: "*.bat", eol: "dos")
492 })
493
494 private ifNull(v, defVal) { v != null ? v : defVal }
495
496 binding.setVariable("unixScripts", { String target, String home, String name, Map args ->
497   String fullName = args.fullName != null ? args.fullName : p("component.names.fullname")
498   String product_uc = args.product_uc != null ? args.product_uc : p("component.names.product").toUpperCase()
499   String vm_options = args.vm_options != null ? args.vm_options : p("component.names.product").toLowerCase()
500
501   String classPath = "CLASSPATH=\"\$IDE_HOME/lib/${classPathLibs[0]}\"\n"
502   classPath += classPathLibs[1..-1].collect {"CLASSPATH=\"\$CLASSPATH:\$IDE_HOME/lib/${it}\""}.join("\n")
503   if (args.tools_jar) classPath += "\nCLASSPATH=\"\$CLASSPATH:\$JDK/lib/tools.jar\""
504
505   ant.copy(todir: "$target/bin") {
506     fileset(dir: "$home/bin/scripts/unix")
507
508     filterset(begintoken: "@@", endtoken: "@@") {
509       filter(token: "product_full", value: fullName)
510       filter(token: "product_uc", value: product_uc)
511       filter(token: "vm_options", value: vm_options)
512       filter(token: "isEap", value: isEap())
513       filter(token: "system_selector", value: args.system_selector)
514       filter(token: "ide_jvm_args", value: ifNull(args.ide_jvm_args, ""))
515       filter(token: "class_path", value: classPath)
516       filter(token: "script_name", value: name)
517     }
518   }
519
520   if (name != "idea.sh") {
521     ant.move(file: "$target/bin/idea.sh", tofile: "$target/bin/$name")
522   }
523   if (args.inspect_script != null && args.inspect_script != "inspect") {
524     ant.move(file: "$target/bin/inspect.sh", tofile: "$target/bin/${args.inspect_script}.sh")
525   }
526
527   ant.fixcrlf(srcdir: "$target/bin", includes: "*.sh", eol: "unix")
528 })
529
530 binding.setVariable("winVMOptions", { String target, String system_selector, String name, String name64 = null ->
531   if (name != null) {
532     def options = isEap() && system_selector != null ? vmOptions32yjp(system_selector) : vmOptions32()
533     ant.echo(file: "$target/bin/${name}.vmoptions", message: options.replace(' ', '\n'))
534   }
535
536   if (name64 != null) {
537     def options = isEap() && system_selector != null ? vmOptions64yjp(system_selector) : vmOptions64()
538     ant.echo(file: "$target/bin/${name64}.vmoptions", message: options.replace(' ', '\n'))
539   }
540
541   ant.fixcrlf(srcdir: "$target/bin", includes: "*.vmoptions", eol: "dos")
542 })
543
544 binding.setVariable("unixVMOptions", { String target, String name, String name64 = (name + "64") ->
545   if (name != null) {
546     ant.echo(file: "$target/bin/${name}.vmoptions", message: "${vmOptions32()} -Dawt.useSystemAAFontSettings=lcd".trim().replace(' ', '\n'))
547   }
548   if (name64 != null) {
549     ant.echo(file: "$target/bin/${name64}.vmoptions", message: "${vmOptions64()} -Dawt.useSystemAAFontSettings=lcd".trim().replace(' ', '\n'))
550   }
551   ant.fixcrlf(srcdir: "$target/bin", includes: "*.vmoptions", eol: "unix")
552 })
553
554 binding.setVariable("unixReadme", { String target, String home, Map args ->
555   String fullName = args.fullName != null ? args.fullName : p("component.names.fullname")
556   String settings_dir = args.system_selector.replaceFirst("\\d+", "")
557   copyAndPatchFile("$home/build/Install-Linux-tar.txt", "$target/Install-Linux-tar.txt",
558                    ["product_full": fullName,
559                     "product": p("component.names.product").toLowerCase(),
560                     "system_selector": args.system_selector,
561                     "settings_dir": settings_dir], "@@")
562   ant.fixcrlf(file: "$target/bin/Install-Linux-tar.txt", eol: "unix")
563 })
564
565 binding.setVariable("forceDelete", { String dirPath ->
566   // if wasn't deleted - retry several times
567   attempt = 1
568   while (attempt < 21 && (new File(dirPath).exists())) {
569     if (attempt > 1) {
570       ant.echo "Deleting $dirPath ... (attempt=$attempt)"
571
572       // let's wait a bit and try again - may be help
573       // in some cases on our windows 7 agents
574       sleep(2000)
575     }
576
577     ant.delete(failonerror: false, dir: dirPath)
578
579     attempt++
580   }
581
582   if (new File(dirPath).exists()) {
583     ant.project.log ("Cannot delete directory: $dirPath" )
584     System.exit (1)
585   }
586 })
587
588 binding.setVariable("patchPropertiesFile", { String target, Map args = [:] ->
589   String file = "$target/bin/idea.properties"
590
591   if (args.appendices != null) {
592     ant.concat(destfile: file, append:  true) {
593       args.appendices.each {
594         fileset(file: it)
595       }
596     }
597   }
598
599   String product_uc = args.product_uc != null ? args.product_uc : p("component.names.product").toUpperCase()
600   String settings_dir = args.system_selector.replaceFirst("\\d+", "")
601   ant.replace(file: file) {
602     replacefilter(token: "@@product_uc@@", value: product_uc)
603     replacefilter(token: "@@settings_dir@@", value: settings_dir)
604   }
605
606   String message = (isEap() ? """
607 #-----------------------------------------------------------------------
608 # Change to 'disabled' if you don't want to receive instant visual notifications
609 # about fatal errors that happen to an IDE or plugins installed.
610 #-----------------------------------------------------------------------
611 idea.fatal.error.notification=enabled
612 """
613                  : """
614 #-----------------------------------------------------------------------
615 # Change to 'enabled' if you want to receive instant visual notifications
616 # about fatal errors that happen to an IDE or plugins installed.
617 #-----------------------------------------------------------------------
618 idea.fatal.error.notification=disabled
619 """)
620   ant.echo(file: file, append: true, message: message)
621 })
622
623 binding.setVariable("zipSources", { String home, String targetDir ->
624   String sources = "$targetDir/sources.zip"
625   projectBuilder.stage("zip sources to $sources")
626
627   ant.mkdir(dir: targetDir)
628   ant.delete(file: sources)
629   ant.zip(destfile: sources) {
630     fileset(dir: home) {
631       ["java", "groovy", "ipr", "iml", "form", "xml", "properties"].each {
632         include(name: "**/*.$it")
633       }
634       exclude(name: "**/testData/**")
635     }
636   }
637
638   notifyArtifactBuilt(sources)
639 })
640
641 binding.setVariable("zipSourcesOfModules", { String home, String targetFilePath, Collection<String> modules ->
642   projectBuilder.stage("zip sources of ${modules.size()} modules to $targetFilePath")
643
644   ant.mkdir(dir: new File(targetFilePath).getParent())
645   ant.delete(file: targetFilePath)
646   ant.zip(destfile: targetFilePath) {
647     modules.each {
648       JpsModule module = findModule(it)
649       module.getSourceRoots(JavaSourceRootType.SOURCE).flatten().collect {it.file}.each { File srcRoot ->
650         zipfileset(prefix: module.name, dir: srcRoot.absolutePath)
651       }
652     }
653   }
654
655   notifyArtifactBuilt(targetFilePath)
656 })
657
658 /**
659  * E.g.
660  *
661  * Load all properties from file:
662  *    readIdeaProperties("idea.properties.path" : "$home/ruby/build/idea.properties")
663  *
664  * Load all properties except "idea.cycle.buffer.size", change "idea.max.intellisense.filesize" to 3000
665  * and enable "idea.is.internal" mode:
666  *    readIdeaProperties("idea.properties.path" : "$home/ruby/build/idea.properties",
667  *                       "idea.properties" : ["idea.max.intellisense.filesize" : 3000,
668  *                                           "idea.cycle.buffer.size" : null,
669  *                                           "idea.is.internal" : true ])
670  * @param args
671  * @return text xml properties description in xml
672  */
673 private Map readIdeaProperties(Map args) {
674   String ideaPropertiesPath =  args == null ? null : args.get("idea.properties.path")
675   if (ideaPropertiesPath == null) {
676     return [:]
677   }
678
679   // read idea.properties file
680   Properties ideaProperties = new Properties();
681   FileInputStream ideaPropertiesFile = new FileInputStream(ideaPropertiesPath);
682   ideaProperties.load(ideaPropertiesFile);
683   ideaPropertiesFile.close();
684
685   def defaultProperties = ["CVS_PASSFILE": "~/.cvspass",
686                            "JVMVersion": "1.6*",
687                            "com.apple.mrj.application.live-resize": "false",
688                            "idea.paths.selector": args.system_selector,
689                            "java.endorsed.dirs": "",
690                            "idea.smooth.progress": "false",
691                            "apple.laf.useScreenMenuBar": "true",
692                            "apple.awt.graphics.UseQuartz": "true",
693                            "apple.awt.fullscreencapturealldisplays": "false"]
694   if (args.platform_prefix != null) {
695     defaultProperties.put("idea.platform.prefix", args.platform_prefix)
696   }
697
698   Map properties = defaultProperties
699   def customProperties = args.get("idea.properties")
700   if (customProperties != null) {
701     properties += customProperties
702   }
703
704   properties.each {k, v ->
705     if (v == null) {
706       // if overridden with null - ignore property
707       ideaProperties.remove(k)
708     } else {
709       // if property is overridden in args map - use new value
710       ideaProperties.put(k, v)
711     }
712   }
713
714   return ideaProperties;
715 }
716
717 private String submapToXml(Map properties, List keys) {
718 // generate properties description for Info.plist
719   StringBuilder buff = new StringBuilder()
720
721   keys.each { key ->
722     String value = properties[key]
723     if (value != null) {
724       String string =
725         """
726         <key>$key</key>
727         <string>$value</string>
728 """
729       buff.append(string)
730     }
731   }
732   return buff.toString()
733 }
734
735 private List<File> getChildren(File file) {
736   if (!file.isDirectory()) return []
737   return file.listFiles().sort { File f -> f.name.toLowerCase() }
738 }
739
740 binding.setVariable("getCustomJDK", { File customJdkDir ->
741   getChildren(customJdkDir).each {
742     if (it.getName().endsWith(".tar.gz")) {
743       def customJdkFileName = it.getAbsolutePath().substring(0, it.getAbsolutePath().length() - 3)
744       if (new File(customJdkFileName).exists()){ ant.delete(file: customJdkFileName) }
745       if (new File("${home}/build/jdk/custom_jdk_${buildNumber}.tar").exists()){
746         ant.delete(file: "${home}/build/jdk/custom_jdk_${buildNumber}.tar")
747       }
748       ant.gunzip(src: it.getAbsolutePath())
749       projectBuilder.info("customJdkFileName: " + customJdkFileName)
750       ant.copy(file: customJdkFileName, tofile: "${home}/build/jdk/custom_jdk_${buildNumber}.tar")
751     }
752   }
753 })
754
755 binding.setVariable("buildWinZip", { String zipPath, List paths ->
756   projectBuilder.stage(".win.zip")
757
758   fixIdeaPropertiesEol(paths, "dos")
759
760   ant.zip(zipfile: zipPath) {
761     paths.each {
762       fileset(dir: it)
763     }
764   }
765
766   notifyArtifactBuilt(zipPath)
767 })
768
769 binding.setVariable("buildMacZip", { String zipRoot, String zipPath, List paths, String macPath, List extraBins = [] ->
770   projectBuilder.stage(".mac.zip")
771
772   allPaths = paths + [macPath]
773   ant.zip(zipfile: zipPath) {
774     allPaths.each {
775       zipfileset(dir: it, prefix: zipRoot) {
776         exclude(name: "bin/*.sh")
777         exclude(name: "bin/*.py")
778         exclude(name: "bin/fsnotifier")
779         exclude(name: "bin/restarter")
780         exclude(name: "MacOS/*")
781         extraBins.each {
782           exclude(name: it)
783         }
784         exclude(name: "bin/idea.properties")
785       }
786     }
787
788     allPaths.each {
789       zipfileset(dir: it, filemode: "755", prefix: zipRoot) {
790         include(name: "bin/*.sh")
791         include(name: "bin/*.py")
792         include(name: "bin/fsnotifier")
793         include(name: "bin/restarter")
794         include(name: "MacOS/*")
795         extraBins.each {
796           include(name: it)
797         }
798       }
799     }
800
801     zipfileset(file: "$macPath/bin/idea.properties", prefix: "$zipRoot/bin")
802   }
803 })
804
805 binding.setVariable("buildTarGz", { String tarRoot, String tarPath, List paths, List extraBins = [] ->
806   projectBuilder.stage(".tar.gz")
807
808   fixIdeaPropertiesEol(paths, "unix")
809
810   ant.tar(tarfile: tarPath, longfile: "gnu") {
811     paths.each {
812       tarfileset(dir: it, prefix: tarRoot) {
813         exclude(name: "bin/*.sh")
814         exclude(name: "bin/fsnotifier*")
815         extraBins.each {
816           exclude(name: it)
817         }
818         type(type: "file")
819       }
820     }
821
822     paths.each {
823       tarfileset(dir: it, filemode: "755", prefix: tarRoot) {
824         include(name: "bin/*.sh")
825         include(name: "bin/fsnotifier*")
826         extraBins.each {
827           include(name: it)
828         }
829         type(type: "file")
830       }
831     }
832   }
833
834   String gzPath = "${tarPath}.gz"
835   ant.gzip(src: tarPath, zipfile: gzPath)
836   ant.delete(file: tarPath)
837   notifyArtifactBuilt(gzPath)
838 })
839
840 private void fixIdeaPropertiesEol(List paths, String eol) {
841   paths.each {
842     String file = "$it/bin/idea.properties"
843     if (new File(file).exists()) {
844       ant.fixcrlf(file: file, eol: eol)
845     }
846   }
847 }
848
849 binding.setVariable("buildWinLauncher", { String ch, String inputPath, String outputPath, String appInfo,
850                                           String launcherProperties, String pathsSelector, List resourcePaths ->
851   projectBuilder.stage("winLauncher")
852
853   if (pathsSelector != null) {
854     def paths = getProperty("paths")
855     def launcherPropertiesTemp = "${paths.sandbox}/launcher.properties"
856     copyAndPatchFile(launcherProperties, launcherPropertiesTemp, ["PRODUCT_PATHS_SELECTOR": pathsSelector])
857     launcherProperties = launcherPropertiesTemp
858   }
859
860   ant.java(classname: "com.pme.launcher.LauncherGeneratorMain", fork: "true", failonerror: "true") {
861     sysproperty(key: "java.awt.headless", value: "true")
862     arg(value: inputPath)
863     arg(value: appInfo)
864     arg(value: "$ch/native/WinLauncher/WinLauncher/resource.h")
865     arg(value: launcherProperties)
866     arg(value: outputPath)
867     classpath {
868       pathelement(location: "$ch/build/lib/launcher-generator.jar")
869       fileset(dir: "$ch/lib") {
870         include(name: "guava*.jar")
871         include(name: "jdom.jar")
872         include(name: "sanselan*.jar")
873       }
874       resourcePaths.each {
875         pathelement(location: it)
876       }
877     }
878   }
879 })
880
881 binding.setVariable("collectUsedJars", { List modules, List approvedJars, List forbiddenJars, List modulesToBuild ->
882   def usedJars = new HashSet();
883
884   modules.each {
885     def module = findModule(it)
886     if (module != null) {
887       projectBuilder.moduleRuntimeClasspath(module, false).each {
888         File file = new File(it)
889         if (file.exists()) {
890           String path = file.canonicalPath.replace('\\', '/')
891           if (path.endsWith(".jar") && approvedJars.any { path.startsWith(it) } && !forbiddenJars.any { path.contains(it) }) {
892             if (usedJars.add(path)) {
893               projectBuilder.info("\tADDED: $path for ${module.getName()}")
894             }
895           }
896         }
897       }
898       if (modulesToBuild != null) {
899         modulesToBuild << module
900       }
901     }
902     else {
903       projectBuilder.warning("$it is not a module")
904     }
905   }
906
907   return usedJars
908 })
909
910 binding.setVariable("buildModulesAndCollectUsedJars", { List modules, List approvedJars, List forbiddenJars ->
911   def modulesToBuild = []
912   def usedJars = collectUsedJars(modules, approvedJars, forbiddenJars, modulesToBuild)
913   projectBuilder.cleanOutput()
914   projectBuilder.buildModules(modulesToBuild)
915
916   return usedJars
917 })
918
919 binding.setVariable("buildSearchableOptions", { String target, List licenses, Closure cp, String jvmArgs = null,
920                                                 def paths = getProperty("paths") ->
921   projectBuilder.stage("Building searchable options")
922
923   String targetFile = "${target}/searchableOptions.xml"
924   ant.delete(file: targetFile)
925
926   licenses.each {
927     ant.copy(file: it, todir: paths.ideaSystem)
928   }
929
930   ant.path(id: "searchable.options.classpath") { cp() }
931   String classpathFile = "${paths.sandbox}/classpath.txt"
932   ant.echo(file: classpathFile, append: false, message: "\${toString:searchable.options.classpath}")
933   ant.replace(file: classpathFile, token: File.pathSeparator, value: "\n")
934
935   ant.java(classname: "com.intellij.rt.execution.CommandLineWrapper", fork: true, failonerror: true) {
936     jvmarg(line: "-ea -Xmx500m -XX:MaxPermSize=200m")
937     jvmarg(value: "-Xbootclasspath/a:${projectBuilder.moduleOutput(findModule("boot"))}")
938     jvmarg(value: "-Didea.home.path=${home}")
939     jvmarg(value: "-Didea.system.path=${paths.ideaSystem}")
940     jvmarg(value: "-Didea.config.path=${paths.ideaConfig}")
941     if (jvmArgs != null) {
942       jvmarg(line: jvmArgs)
943     }
944
945     arg(value: "${classpathFile}")
946     arg(line: "com.intellij.idea.Main traverseUI")
947     arg(value: "${target}/searchableOptions.xml")
948
949     classpath() {
950       pathelement(location: "${projectBuilder.moduleOutput(findModule("java-runtime"))}")
951     }
952   }
953
954   ant.available(file: targetFile, property: "searchable.options.exists");
955   ant.fail(unless: "searchable.options.exists", message: "Searchable options were not built.")
956 })
957
958 binding.setVariable("reassignAltClickToMultipleCarets", {String communityHome ->
959   String defaultKeymapContent = new File("$communityHome/platform/platform-resources/src/idea/Keymap_Default.xml").text
960   defaultKeymapContent = defaultKeymapContent.replace("<mouse-shortcut keystroke=\"alt button1\"/>", "")
961   defaultKeymapContent = defaultKeymapContent.replace("<mouse-shortcut keystroke=\"alt shift button1\"/>",
962                                                       "<mouse-shortcut keystroke=\"alt button1\"/>")
963   patchedKeymapFile = new File("${paths.sandbox}/classes/production/platform-resources/idea/Keymap_Default.xml")
964   patchedKeymapFile.write(defaultKeymapContent)
965 })
966
967 // modules used in Upsource and in Kotlin as an API to IDEA
968 binding.setVariable("analysisApiModules", [
969     "analysis-api",
970     "boot",
971     "core-api",
972     "duplicates-analysis",
973     "editor-ui-api",
974     "editor-ui-ex",
975     "extensions",
976     "indexing-api",
977     "java-analysis-api",
978     "java-indexing-api",
979     "java-psi-api",
980     "java-structure-view",
981     "jps-model-api",
982     "jps-model-serialization",
983     "projectModel-api",
984     "structure-view-api",
985     "util",
986     "util-rt",
987     "xml-analysis-api",
988     "xml-psi-api",
989     "xml-structure-view-api",
990 ])
991 binding.setVariable("analysisImplModules", [
992     "analysis-impl",
993     "core-impl",
994     "indexing-impl",
995     "java-analysis-impl",
996     "java-indexing-impl",
997     "java-psi-impl",
998     "projectModel-impl",
999     "structure-view-impl",
1000     "xml-analysis-impl",
1001     "xml-psi-impl",
1002     "xml-structure-view-impl",
1003 ])