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