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