[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / platform / build-scripts / groovy / org / jetbrains / intellij / build / impl / BundledJreManager.groovy
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package org.jetbrains.intellij.build.impl
3
4 import com.intellij.openapi.util.SystemInfo
5 import groovy.transform.CompileDynamic
6 import groovy.transform.CompileStatic
7 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
8 import org.apache.commons.compress.compressors.CompressorStreamFactory
9 import org.jetbrains.intellij.build.BuildContext
10 import org.jetbrains.intellij.build.JvmArchitecture
11
12 import java.util.concurrent.ConcurrentHashMap
13
14 /**
15  * @author nik
16  */
17 @CompileStatic
18 class BundledJreManager {
19   private final BuildContext buildContext
20   String baseDirectoryForJre
21
22   BundledJreManager(BuildContext buildContext, String baseDirectoryForJre) {
23     this.buildContext = buildContext
24     this.baseDirectoryForJre = baseDirectoryForJre
25   }
26
27   /**
28    * Extract JRE for Linux distribution of the product
29    * @return path to the directory containing 'jre' subdirectory with extracted JRE
30    */
31   String extractLinuxJre() {
32     return extractJre("linux")
33   }
34
35   private static boolean doBundleSecondJre() {
36     return System.getProperty('intellij.build.bundle.second.jre', 'false').toBoolean()
37   }
38
39   String getSecondJreBuild() {
40     if (!doBundleSecondJre()) return null
41     def build = System.getProperty("intellij.build.bundled.second.jre.build")
42     if (build == null) {
43       loadDependencyVersions()
44       build = dependencyVersions.get('secondJreBuild')
45     }
46     return build
47   }
48
49   String getSecondJreVersion() {
50     if (!doBundleSecondJre()) return null
51     def version = System.getProperty("intellij.build.bundled.second.jre.version")
52     if (version == null) {
53       loadDependencyVersions()
54       version = dependencyVersions.get('secondJreVersion')
55     }
56     return version
57   }
58
59   String extractSecondJre(String osName, String secondJreBuild) {
60     String targetDir = "$baseDirectoryForJre/secondJre.${osName}_${JvmArchitecture.x64}"
61     if (new File(targetDir).exists()) {
62       buildContext.messages.info("JRE is already extracted to $targetDir")
63       return targetDir
64     }
65     def jreArchive = "jbr-${jreArchiveSuffix(secondJreBuild, getSecondJreVersion(), JvmArchitecture.x64, osName)}"
66     File archive = new File(jreDir(), jreArchive)
67     if (!archive.file || !archive.exists()) {
68       def errorMessage = "Cannot extract $osName JRE: file $jreArchive is not found"
69       buildContext.messages.warning(errorMessage)
70     }
71     if (archive == null) {
72       return null
73     }
74
75     buildContext.messages.block("Extract $archive.absolutePath JRE") {
76       String destination = "$targetDir/jre64"
77       def destinationDir = new File(destination)
78       if (destinationDir.exists()) destinationDir.deleteDir()
79       untar(archive, destination, isSecondBundledJreModular())
80     }
81     return targetDir
82   }
83
84   /**
85    * Extract JRE for Windows distribution of the product
86    * @return path to the directory containing 'jre' subdirectory with extracted JRE
87    */
88   String extractWinJre(JvmArchitecture arch) {
89     return extractJre("windows", arch)
90   }
91
92   /**
93    * Extract Oracle JRE for Windows distribution of the product
94    * @return path to the directory containing 'jre' subdirectory with extracted JRE
95    */
96   String extractOracleWinJre(JvmArchitecture arch) {
97     return extractJre("windows", arch, JreVendor.Oracle)
98   }
99
100   /**
101    * Return path to a .tar.gz archive containing distribution of JRE for macOS which will be bundled with the product
102    */
103   String findMacJreArchive() {
104     return findJreArchive("osx")?.absolutePath
105   }
106
107   /**
108    * Return a .tar.gz archive containing distribution of JRE for Win OS which will be bundled with the product
109    */
110   File findWinJreArchive(JvmArchitecture arch) {
111     return findJreArchive("windows", arch)
112   }
113
114   String archiveNameJre(BuildContext buildContext) {
115     return "jre-for-${buildContext.buildNumber}.tar.gz"
116   }
117
118   private String extractJre(String osName, JvmArchitecture arch = JvmArchitecture.x64, JreVendor vendor = JreVendor.JetBrains) {
119     String vendorSuffix = vendor == JreVendor.Oracle ? ".oracle" : ""
120     String targetDir = arch == JvmArchitecture.x64 ?
121                        "$baseDirectoryForJre/jre.$osName$arch.fileSuffix$vendorSuffix" :
122                        "$baseDirectoryForJre/jre.${osName}32$vendorSuffix"
123     if (new File(targetDir).exists()) {
124       buildContext.messages.info("JRE is already extracted to $targetDir")
125       return targetDir
126     }
127
128     File archive = findJreArchive(osName, arch, vendor)
129     if (archive == null) {
130       return null
131     }
132     buildContext.messages.block("Extract $archive.name JRE") {
133       String destination = "$targetDir/jre64"
134       if (osName == "windows" && arch == JvmArchitecture.x32) {
135         destination = "$targetDir/jre32"
136       }
137       buildContext.messages.progress("Extracting JRE from '$archive.name' archive")
138       untar(archive, destination, isBundledJreModular())
139     }
140     return targetDir
141   }
142
143   /**
144    * @param archive linux or windows JRE archive
145    */
146   @CompileDynamic
147   private def untar(File archive, String destination, boolean isModular) {
148     // strip `jre` root directory for jbr8
149     def stripRootDir = !isModular ||
150                        // or `jbr` root directory for jbr11+
151                        buildContext.bundledJreManager.hasJbrRootDir(archive)
152     if (SystemInfo.isWindows) {
153       buildContext.ant.untar(src: archive.absolutePath, dest: destination, compression: 'gzip') {
154         if (stripRootDir) {
155           cutdirsmapper(dirs: 1)
156         }
157       }
158     }
159     else {
160       // 'tar' command is used instead of Ant task to ensure that executable flags will be preserved
161       buildContext.ant.mkdir(dir: destination)
162       buildContext.ant.exec(executable: "tar", dir: archive.parent) {
163         arg(value: "-xf")
164         arg(value: archive.name)
165         if (stripRootDir) {
166           arg(value: "--strip")
167           arg(value: "1")
168         }
169         arg(value: "--directory")
170         arg(value: destination)
171       }
172     }
173   }
174
175   private File dependenciesDir() {
176     new File(buildContext.paths.communityHome, 'build/dependencies')
177   }
178
179   File jreDir() {
180     def dependenciesDir = dependenciesDir()
181     new File(dependenciesDir, 'build/jbre')
182   }
183
184   /**
185    * Update this method together with:
186    *  `build/dependencies/setupJbre.gradle`
187    *  `build/dependencies/setupJdk.gradle`
188   */
189   static String jreArchiveSuffix(String jreBuild, String version, JvmArchitecture arch, String osName) {
190     String update, build
191     def split = jreBuild.split('b')
192     if (split.length > 2) {
193       throw new IllegalArgumentException(
194         "$jreBuild is expected in format <update>b<build_number>. Examples: u202b1483.24, 11_0_2b140, b96"
195       )
196     }
197     if (split.length == 2) {
198       update = split[0]
199       if (update.startsWith(version)) update -= version
200       // [11_0_2, b140] or [8u202, b1483.24]
201       (update, build) = ["$version$update", "b${split[1]}"]
202     }
203     else {
204       // [11, b96]
205       (update, build) = [version, jreBuild]
206     }
207     "${update}-${osName}-${arch == JvmArchitecture.x32 ? 'i586' : 'x64'}-${build}.tar.gz"
208   }
209
210   private File findJreArchive(String osName, JvmArchitecture arch = JvmArchitecture.x64, JreVendor vendor = JreVendor.JetBrains) {
211     def jreDir = jreDir()
212     def jreBuild = getExpectedJreBuild(osName)
213
214     String suffix = jreArchiveSuffix(jreBuild, buildContext.options.bundledJreVersion.toString(), arch, osName)
215     String prefix = System.getProperty("intellij.build.bundled.jre.prefix")
216     if (prefix == null) {
217       prefix = isBundledJreModular() ? vendor.jreNamePrefix :
218                buildContext.productProperties.toolsJarRequired ? vendor.jreWithToolsJarNamePrefix : vendor.jreNamePrefix
219     }
220     def jreArchive = new File(jreDir, "$prefix$suffix")
221
222     if (!jreArchive.file || !jreArchive.exists()) {
223       def errorMessage = "Cannot extract $osName JRE: file $jreArchive is not found (${jreDir.listFiles()})"
224       if (buildContext.options.isInDevelopmentMode) {
225         buildContext.messages.warning(errorMessage)
226       }
227       else {
228         buildContext.messages.error(errorMessage)
229       }
230       return null
231     }
232     return jreArchive
233   }
234
235   private Properties dependencyVersions
236   private synchronized void loadDependencyVersions() {
237     if (dependencyVersions == null) {
238       buildContext.gradle.run('Preparing dependencies file', 'dependenciesFile')
239
240       def stream = new File(dependenciesDir(), 'build/dependencies.properties').newInputStream()
241       try {
242         Properties properties = new Properties()
243         properties.load(stream)
244         dependencyVersions = properties
245       }
246       finally {
247         stream.close()
248       }
249     }
250   }
251
252   private String getExpectedJreBuild(String osName) {
253     loadDependencyVersions()
254     return dependencyVersions.get("jreBuild_${osName}" as String, buildContext.options.bundledJreBuild ?: dependencyVersions.get("jdkBuild", ""))
255   }
256
257   private enum JreVendor {
258     Oracle("jre", "jdk"),
259     JetBrains("jbr-", "jbrx-")
260
261     final String jreNamePrefix
262     final String jreWithToolsJarNamePrefix
263
264     JreVendor(String jreNamePrefix, String jreWithToolsJarNamePrefix) {
265       this.jreNamePrefix = jreNamePrefix
266       this.jreWithToolsJarNamePrefix = jreWithToolsJarNamePrefix
267     }
268   }
269
270   String jreSuffix() {
271     isBundledJreModular() ? "" : "-jbr8"
272   }
273
274   boolean is32bitArchSupported() {
275     !isBundledJreModular()
276   }
277
278   /**
279    *  If {@code true} then bundled JRE version is 9+
280    */
281   boolean isBundledJreModular() {
282     return buildContext.options.bundledJreVersion >= 9
283   }
284
285   /**
286    *  If {@code true} then second bundled JRE version is 9+
287    */
288   boolean isSecondBundledJreModular() {
289     return secondJreVersion.toInteger() >= 9
290   }
291
292   private final Map<File, Boolean> jbrArchiveInspectionCache = new ConcurrentHashMap<>()
293
294   /**
295    * If {@code true} then JRE top directory was renamed to JBR, see JBR-1295
296    */
297   boolean hasJbrRootDir(File archive) {
298     jbrArchiveInspectionCache.computeIfAbsent(archive) {
299       def tarArchive = new TarArchiveInputStream(
300         new CompressorStreamFactory().createCompressorInputStream(
301           new BufferedInputStream(new FileInputStream(archive))
302         ))
303       def entry = tarArchive.nextTarEntry?.name
304       if (entry == null) throw new IllegalStateException("Unable to read $archive")
305       entry.startsWith('jbr')
306     }
307   }
308 }