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