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