diff --git a/build/conf/ideaCE.properties b/build/conf/ideaCE.properties index 8c86bf315b02..6c9054459166 100644 --- a/build/conf/ideaCE.properties +++ b/build/conf/ideaCE.properties @@ -1,4 +1,3 @@ - #--------------------------------------------------------------------- # IDEA can copy library .jar files to prevent their locking. # By default this behavior is enabled on Windows and disabled on other platforms. diff --git a/build/groovy/org/jetbrains/intellij/build/ApplicationInfoProperties.groovy b/build/groovy/org/jetbrains/intellij/build/ApplicationInfoProperties.groovy index d4af8b3901d9..dd21d5ca283c 100644 --- a/build/groovy/org/jetbrains/intellij/build/ApplicationInfoProperties.groovy +++ b/build/groovy/org/jetbrains/intellij/build/ApplicationInfoProperties.groovy @@ -14,21 +14,25 @@ * limitations under the License. */ package org.jetbrains.intellij.build - -import groovy.transform.Immutable - /** * @author nik */ class ApplicationInfoProperties { final String majorVersion final String minorVersion + final String shortProductName + final String productName + final String companyName final boolean isEAP + @SuppressWarnings("GrUnresolvedAccess") ApplicationInfoProperties(String appInfoXmlPath) { def root = new XmlParser().parse(new File(appInfoXmlPath)) majorVersion = root.version.first().@major minorVersion = root.version.first().@minor + shortProductName = root.names.first().@product + productName = root.names.first().@fullname + companyName = root.company.first().@name isEAP = Boolean.parseBoolean(root.version.first().@eap) } } \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/BuildContext.groovy b/build/groovy/org/jetbrains/intellij/build/BuildContext.groovy new file mode 100644 index 000000000000..c44ce1441da4 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/BuildContext.groovy @@ -0,0 +1,134 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build + +import org.codehaus.gant.GantBuilder +import org.jetbrains.intellij.build.impl.BuildContextImpl +import org.jetbrains.jps.gant.JpsGantProjectBuilder +import org.jetbrains.jps.model.JpsGlobal +import org.jetbrains.jps.model.JpsProject +import org.jetbrains.jps.model.module.JpsModule + +/** + * @author nik + */ +abstract class BuildContext { + GantBuilder ant + BuildMessages messages + BuildPaths paths + JpsProject project + ApplicationInfoProperties applicationInfo + JpsGantProjectBuilder projectBuilder + ProductProperties productProperties + BuildOptions options + + /** + * Build number without product code (e.g. '162.500.10') + */ + String buildNumber + + /** + * Build number with product code (e.g. 'IC-162.500.10') + */ + String fullBuildNumber + + /** + * An identifier which will be used to form names for directories where configuration and caches will be stored, usually a product name + * without spaces with added major version ('IntelliJIdea2016.1' for IntelliJ IDEA 2016.1) + */ + String systemSelector + + /** + * Base name for script files (*.bat, *.sh, *.exe), usually a shortened product name in lower case (e.g. 'idea' for IntelliJ IDEA, 'datagrip' for DataGrip) + */ + String fileNamePrefix + + /** + * Names of JARs inside IDE_HOME/lib directory which need to be added to bootclasspath to start the IDE + */ + List bootClassPathJarNames + + abstract void notifyArtifactBuilt(String artifactPath) + + abstract File findApplicationInfoInSources() + + abstract JpsModule findApplicationInfoModule() + + abstract JpsModule findModule(String name) + + abstract void executeStep(String stepMessage, String stepId, Closure step) + + public static BuildContext createContext(GantBuilder ant, JpsGantProjectBuilder projectBuilder, JpsProject project, JpsGlobal global, + String communityHome, String projectHome, String buildOutputRoot, ProductProperties productProperties, + BuildOptions options = new BuildOptions()) { + return new BuildContextImpl(ant, projectBuilder, project, global, communityHome, projectHome, buildOutputRoot, productProperties, options) + } +} + +abstract class BuildPaths { + /** + * Path to a directory where idea/community Git repository is checked out + */ + String communityHome + + /** + * Path to a base directory of the project which will be compiled + */ + String projectHome + + /** + * Path to a directory where build script will store temporary and resulting files + */ + String buildOutputRoot + + /** + * Path to a directory where resulting artifacts will be placed + */ + String artifacts + + /** + * Path to a directory containing distribution files ('bin', 'lib', 'plugins' directories) common for all operating systems + */ + String distAll + + /** + * Path to a directory where temporary files required for a particular build steps can be stored + */ + String temp + + /** + * Path to a directory containing distribution of JRE for Windows which will be bundled with the product + */ + String winJre + + /** + * Path to a directory containing distribution of JRE for Linux which will be bundled with the product + */ + String linuxJre +} + +interface BuildMessages { + void info(String message) + void warning(String message) + + /** + * Report an error and stop the build process + */ + void error(String message) + + void progress(String message) + void block(String blockName, Closure body) +} \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/BuildOptions.groovy b/build/groovy/org/jetbrains/intellij/build/BuildOptions.groovy new file mode 100644 index 000000000000..a0e4585185b7 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/BuildOptions.groovy @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build + +import com.intellij.util.SystemProperties + +/** + * @author nik + */ +class BuildOptions { + /** + * By default build scripts compile project classes to a special output directory (to not interfere with the default project output if + * invoked on a developer machine). Pass 'true' to this system property to skip compilation step and use compiled classes from the project output instead. + */ + public static final String USE_COMPILED_CLASSES_PROPERTY = "intellij.build.useCompiledClasses" + boolean useCompiledClassesFromProjectOutput = SystemProperties.getBooleanProperty(USE_COMPILED_CLASSES_PROPERTY, false) + + /** + * Pass comma-separated names of build steps (see below) to 'intellij.build.skipBuildSteps' system property to skip them when building locally. + */ + Set buildStepsToSkip = System.getProperty("intellij.build.skipBuildSteps", "").split(",") as Set + static final SEARCHABLE_OPTIONS_INDEX_STEP = "search_index" + static final SOURCES_ARCHIVE_STEP = "sources_archive" + static final MAC_DISTRIBUTION_STEP = "mac_dist" + static final LINUX_DISTRIBUTION_STEP = "linux_dist" + static final WINDOWS_DISTRIBUTION_STEP = "windows_dist" + static final CROSS_PLATFORM_DISTRIBUTION_STEP = "cross_platform_dist" +} \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/BuildTasks.groovy b/build/groovy/org/jetbrains/intellij/build/BuildTasks.groovy new file mode 100644 index 000000000000..68ebd05a9f19 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/BuildTasks.groovy @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build + +import org.jetbrains.intellij.build.impl.BuildTasksImpl + +/** + * @author nik + */ +abstract class BuildTasks { + /** + * Build archive of the project source files keeping the original layout + */ + abstract void zipSources() + + /** + * Update search/searchableOptions.xml file in {@code targetModuleName} module output directory + */ + abstract void buildSearchableOptions(String targetModuleName, List modulesToIndex, List pathsToLicenses) + + /** + * Create a copy of *ApplicationInfo.xml file with substituted __BUILD_NUMBER__ and __BUILD_DATE__ placeholders + * @return path to the copied file + */ + abstract File patchApplicationInfo() + + /** + * Create distribution for all operating system from JAR files located at {@link BuildPaths#distAll} + */ + abstract void buildDistributions() + + abstract void cleanOutput() + + abstract void compileProjectAndTests(List includingTestsInModules = []) + + public static BuildTasks create(BuildContext context) { + return new BuildTasksImpl(context) + } +} \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/ProductProperties.groovy b/build/groovy/org/jetbrains/intellij/build/ProductProperties.groovy index b1da574142ea..13794e73b33f 100644 --- a/build/groovy/org/jetbrains/intellij/build/ProductProperties.groovy +++ b/build/groovy/org/jetbrains/intellij/build/ProductProperties.groovy @@ -22,43 +22,40 @@ public abstract class ProductProperties { String prefix String code String appInfoModule - String appInfoModulePath + String customInspectScriptName abstract def String appInfoFile() /** - * @return build number with product code (e.g. IC-142.239 for IDEA Community) + * Return {@code true} if tools.jar from JDK must be added to IDE's classpath */ - abstract def String fullBuildNumber() + boolean toolsJarRequired = false - abstract def String systemSelector() + abstract def String systemSelector(ApplicationInfoProperties applicationInfo) + String additionalIDEPropertiesFilePath String exe_launcher_properties String exe64_launcher_properties String platformPrefix = null - String bundleIdentifier - abstract def String macAppRoot() + abstract def String macAppRoot(ApplicationInfoProperties applicationInfo, String buildNumber) - abstract def String winAppRoot() + abstract def String winAppRoot(String buildNumber) - abstract def String linuxAppRoot() + abstract def String linuxAppRoot(String buildNumber) - abstract def String archiveName() + abstract def String archiveName(String buildNumber) boolean setPluginAndIDEVersionInPluginXml = true - String ideJvmArgs = null + String ideJvmArgs = "" boolean maySkipAndroidPlugin String relativeAndroidHome String relativeAndroidToolsBaseHome boolean includeYourkitAgentInEAP = false - boolean includeBatchLauncher = true boolean buildUpdater = false List excludedPlugins = [] - List extraMacBins = [] - List extraLinuxBins = [] def customLayout(targetDirectory) {} @@ -71,4 +68,28 @@ public abstract class ProductProperties { String icon128 String ico String icns + + WindowsProductProperties windows = new WindowsProductProperties() + MacProductProperties mac = new MacProductProperties() + LinuxProductProperties linux = new LinuxProductProperties() } + +class WindowsProductProperties { + boolean includeBatchLauncher = true + boolean bundleJre = true +} + +class MacProductProperties { + String minOSXVersion = "10.8" + String helpId = "" + String docTypes = null + List urlSchemes = [] + List architectures = ["x86_64"] + boolean includeYourkitAgentInEAP = true + List extraMacBins = [] + String bundleIdentifier +} + +class LinuxProductProperties { + List extraLinuxBins = [] +} \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/impl/BuildContextImpl.groovy b/build/groovy/org/jetbrains/intellij/build/impl/BuildContextImpl.groovy new file mode 100644 index 000000000000..1df51075f474 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/BuildContextImpl.groovy @@ -0,0 +1,175 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil +import com.intellij.util.SystemProperties +import org.codehaus.gant.GantBuilder +import org.jetbrains.intellij.build.ApplicationInfoProperties +import org.jetbrains.intellij.build.BuildContext +import org.jetbrains.intellij.build.BuildOptions +import org.jetbrains.intellij.build.BuildPaths +import org.jetbrains.intellij.build.ProductProperties +import org.jetbrains.jps.gant.JpsGantProjectBuilder +import org.jetbrains.jps.model.JpsGlobal +import org.jetbrains.jps.model.JpsProject +import org.jetbrains.jps.model.java.JpsJavaExtensionService +import org.jetbrains.jps.model.module.JpsModule +import org.jetbrains.jps.model.serialization.JpsModelSerializationDataService +import org.jetbrains.jps.model.serialization.JpsProjectLoader +import org.jetbrains.jps.util.JpsPathUtil + +/** + * @author nik + */ +class BuildContextImpl extends BuildContext { + private final JpsGlobal global + private final underTeamCity + + BuildContextImpl(GantBuilder ant, JpsGantProjectBuilder projectBuilder, JpsProject project, JpsGlobal global, + String communityHome, String projectHome, String buildOutputRoot, ProductProperties productProperties, + BuildOptions options = new BuildOptions()) { + this.projectBuilder = projectBuilder + this.ant = ant + this.project = project + this.global = global + this.productProperties = productProperties + this.options = options + underTeamCity = System.getProperty("teamcity.buildType.id") != null + messages = new BuildMessagesImpl(projectBuilder, ant.project, underTeamCity) + + paths = new BuildPathsImpl(communityHome, projectHome, buildOutputRoot) + + loadProject() + def appInfoFile = findApplicationInfoInSources() + applicationInfo = new ApplicationInfoProperties(appInfoFile.absolutePath) + + buildNumber = System.getProperty("build.number") ?: readSnapshotBuildNumber() + fullBuildNumber = "$productProperties.code-$buildNumber" + systemSelector = productProperties.systemSelector(applicationInfo) + fileNamePrefix = productProperties.prefix + + bootClassPathJarNames = ["bootstrap.jar", "extensions.jar", "util.jar", "jdom.jar", "log4j.jar", "trove4j.jar", "jna.jar"] + } + + private void loadProject() { + def projectHome = paths.projectHome + JdkUtils.defineJdk(global, "IDEA jdk", JdkUtils.computeJdkHome(messages, "jdkHome", "$projectHome/build/jdk/1.6", "JDK_16_x64")) + JdkUtils.defineJdk(global, "1.8", JdkUtils.computeJdkHome(messages, "jdk8Home", "$projectHome/build/jdk/1.8", "JDK_18_x64")) + def bundledKotlinPath = "$paths.communityHome/build/kotlinc" + if (!new File(bundledKotlinPath, "lib/kotlin-runtime.jar").exists()) { + messages.error("Could not find Kotlin runtime at $bundledKotlinPath/lib/kotlin-runtime.jar: run download_kotlin.gant script to download Kotlin JARs") + } + JpsModelSerializationDataService.getOrCreatePathVariablesConfiguration(global).addPathVariable("KOTLIN_BUNDLED", bundledKotlinPath) + + projectBuilder.buildIncrementally = SystemProperties.getBooleanProperty("jps.build.incrementally", false) + def dataDirName = projectBuilder.buildIncrementally ? ".jps-incremental-build" : ".jps-build-data" + projectBuilder.dataStorageRoot = new File("$projectHome/$dataDirName") + def tempDir = System.getProperty("teamcity.build.tempDir") ?: System.getProperty("java.io.tmpdir") + projectBuilder.setupAdditionalLogging(new File("$tempDir/system/build-log/build.log"), System.getProperty("jps.build.debug.logging.categories", "")) + + def pathVariables = JpsModelSerializationDataService.computeAllPathVariables(global) + JpsProjectLoader.loadProject(project, pathVariables, projectHome) + projectBuilder.exportModuleOutputProperties() + messages.info("Loaded project $projectHome: ${project.modules.size()} modules, ${project.libraryCollection.libraries.size()} libraries") + if (!options.useCompiledClassesFromProjectOutput) { + projectBuilder.targetFolder = "$paths.buildOutputRoot/classes" + } + else { + def outputDir = JpsPathUtil.urlToFile(JpsJavaExtensionService.getInstance().getOrCreateProjectExtension(project).outputUrl) + if (!outputDir.exists()) { + messages.error("$BuildOptions.USE_COMPILED_CLASSES_PROPERTY is enabled, but the project output directory $outputDir.absolutePath doesn't exist") + } + } + + suppressWarnings() + } + + private void suppressWarnings() { + def compilerOptions = JpsJavaExtensionService.instance.getOrCreateCompilerConfiguration(project).currentCompilerOptions + compilerOptions.GENERATE_NO_WARNINGS = true + compilerOptions.DEPRECATION = false + compilerOptions.ADDITIONAL_OPTIONS_STRING = compilerOptions.ADDITIONAL_OPTIONS_STRING.replace("-Xlint:unchecked", "") + } + + private String readSnapshotBuildNumber() { + new File(paths.communityHome, "build.txt").text.trim() + } + + @Override + File findApplicationInfoInSources() { + JpsModule module = findApplicationInfoModule() + def appInfoRelativePath = "idea/${productProperties.platformPrefix}ApplicationInfo.xml" + def appInfoFile = module.sourceRoots.collect { new File(it.file, appInfoRelativePath) }.find { it.exists() } + if (appInfoFile == null) { + messages.error("Cannot find $appInfoRelativePath in '$module.name' module") + } + return appInfoFile + } + + @Override + JpsModule findApplicationInfoModule() { + def module = findModule(productProperties.appInfoModule) + if (module == null) { + messages.error("Cannot find module '$productProperties.appInfoModule' containing ApplicationInfo.xml file") + } + return module + } + + JpsModule findModule(String name) { + project.modules.find { it.name == name } + } + + @Override + void executeStep(String stepMessage, String stepId, Closure step) { + if (options.buildStepsToSkip.contains(stepId)) { + messages.info("Skipping '$stepMessage'") + } + else { + messages.block(stepMessage, step) + } + } + + @Override + void notifyArtifactBuilt(String artifactPath) { + if (!underTeamCity) return + + if (!FileUtil.startsWith(artifactPath, paths.projectHome)) { + messages.warning("Artifact '$artifactPath' is not under '$paths.projectHome', it won't be reported") + return + } + def relativePath = StringUtil.trimStart(artifactPath.substring(paths.projectHome.length()), "/") + def file = new File(artifactPath) + if (file.isDirectory()) { + relativePath += "=>" + file.name + } + messages.info("##teamcity[publishArtifacts '$relativePath']") + } +} + +class BuildPathsImpl extends BuildPaths { + BuildPathsImpl(String communityHome, String projectHome, String buildOutputRoot) { + this.communityHome = new File(communityHome).canonicalPath + this.projectHome = new File(projectHome).canonicalPath + this.buildOutputRoot = new File(buildOutputRoot).canonicalPath + artifacts = "${this.buildOutputRoot}/artifacts" + distAll = "$buildOutputRoot/dist.all" + temp = "$buildOutputRoot/temp" + winJre = "$buildOutputRoot/jdk.win" + linuxJre = "$buildOutputRoot/jdk.linux" + } +} diff --git a/build/groovy/org/jetbrains/intellij/build/impl/BuildMessagesImpl.groovy b/build/groovy/org/jetbrains/intellij/build/impl/BuildMessagesImpl.groovy new file mode 100644 index 000000000000..b7ad41086ebc --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/BuildMessagesImpl.groovy @@ -0,0 +1,95 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +import com.intellij.openapi.util.text.StringUtil +import org.apache.tools.ant.BuildException +import org.apache.tools.ant.Project +import org.jetbrains.intellij.build.BuildMessages +import org.jetbrains.jps.gant.BuildInfoPrinter +import org.jetbrains.jps.gant.DefaultBuildInfoPrinter +import org.jetbrains.jps.gant.JpsGantProjectBuilder +import org.jetbrains.jps.gant.TeamCityBuildInfoPrinter + +/** + * @author nik + */ +class BuildMessagesImpl implements BuildMessages { + private final JpsGantProjectBuilder builder + private final BuildInfoPrinter buildInfoPrinter + private final Project antProject + private final boolean underTeamCity + private int indent = 0 + + BuildMessagesImpl(JpsGantProjectBuilder builder, Project antProject, boolean underTeamCity) { + this.underTeamCity = underTeamCity + this.antProject = antProject + this.builder = builder + buildInfoPrinter = underTeamCity ? new TeamCityBuildInfoPrinter() : new DefaultBuildInfoPrinter() + } + + @Override + void info(String message) { + antProject.log(withIndent(message), Project.MSG_INFO) + } + + private String withIndent(String message) { + StringUtil.repeat(" ", 2 * indent) + message + } + + @Override + void warning(String message) { + antProject.log(withIndent(message), Project.MSG_WARN) + } + + @Override + void error(String message) { + throw new BuildException(message) + } + + @Override + void progress(String message) { + if (underTeamCity) { + buildInfoPrinter.printProgressMessage(builder, message) + } + else { + info(message) + } + } + + @Override + void block(String blockName, Closure body) { + try { + //todo[nik] move this logic into DefaultBuildInfoPrinter? + if (underTeamCity) { + buildInfoPrinter.printBlockOpenedMessage(builder, blockName) + } + else { + info(blockName) + indent++ + } + body() + } + finally { + if (underTeamCity) { + buildInfoPrinter.printBlockClosedMessage(builder, blockName) + } + else { + indent-- + } + } + } +} \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/impl/BuildTasksImpl.groovy b/build/groovy/org/jetbrains/intellij/build/impl/BuildTasksImpl.groovy new file mode 100644 index 000000000000..5cd73910be41 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/BuildTasksImpl.groovy @@ -0,0 +1,225 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +import com.intellij.openapi.util.io.FileUtil +import org.jetbrains.intellij.build.BuildContext +import org.jetbrains.intellij.build.BuildOptions +import org.jetbrains.intellij.build.BuildTasks + +import java.time.LocalDate +import java.time.format.DateTimeFormatter +/** + * @author nik + */ +class BuildTasksImpl extends BuildTasks { + final BuildContext buildContext + + BuildTasksImpl(BuildContext buildContext) { + this.buildContext = buildContext + } + + @Override + void zipSources() { + buildContext.executeStep("Build sources zip archive", BuildOptions.SOURCES_ARCHIVE_STEP) { + String targetFile = "$buildContext.paths.artifacts/sources.zip" + buildContext.messages.progress("Building sources archive $targetFile") + + buildContext.ant.mkdir(dir: buildContext.paths.artifacts) + buildContext.ant.delete(file: targetFile) + buildContext.ant.zip(destfile: targetFile) { + fileset(dir: buildContext.paths.projectHome) { + ["java", "groovy", "ipr", "iml", "form", "xml", "properties", "kt"].each { + include(name: "**/*.$it") + } + exclude(name: "**/testData/**") + exclude(name: "out/**") + } + } + buildContext.notifyArtifactBuilt(targetFile) + } + } + + //todo[nik] do we need 'cp' and 'jvmArgs' parameters? + @Override + void buildSearchableOptions(String targetModuleName, List modulesToIndex, List pathsToLicenses) { + //todo[nik] create searchableOptions.xml in a separate directory instead of modifying it in the module output + buildContext.executeStep("Build searchable options index", BuildOptions.SEARCHABLE_OPTIONS_INDEX_STEP, { + def javaRuntimeClasses = "${buildContext.projectBuilder.moduleOutput(buildContext.findModule("java-runtime"))}" + if (!new File(javaRuntimeClasses).exists()) { + buildContext.messages. + error("Cannot build searchable options, 'java-runtime' module isn't compiled ($javaRuntimeClasses doesn't exist)") + } + + buildContext.messages.progress("Building searchable options for modules $modulesToIndex") + + def targetModuleOutput = buildContext.projectBuilder.moduleOutput(buildContext.findModule(targetModuleName)) + String targetFile = "$targetModuleOutput/search/searchableOptions.xml" + FileUtil.delete(new File(targetFile)) + + def tempDir = "$buildContext.paths.temp/searchableOptions" + String systemPath = "$tempDir/system" + String configPath = "$tempDir/config" + buildContext.ant.mkdir(dir: tempDir) + pathsToLicenses.each { + //todo[nik] previously licenses were copied to systemPath + buildContext.ant.copy(file: it, todir: configPath) + } + + def ideClasspath = new LinkedHashSet() + modulesToIndex.collectMany(ideClasspath) { buildContext.projectBuilder.moduleRuntimeClasspath(buildContext.findModule(it), false) } + + String classpathFile = "$tempDir/classpath.txt" + new File(classpathFile).text = ideClasspath.join("\n") + + buildContext.ant.java(classname: "com.intellij.rt.execution.CommandLineWrapper", fork: true, failonerror: true) { + jvmarg(line: "-ea -Xmx500m -XX:MaxPermSize=200m") + jvmarg(value: "-Xbootclasspath/a:${buildContext.projectBuilder.moduleOutput(buildContext.findModule("boot"))}") + sysproperty(key: "idea.home.path", value: buildContext.paths.projectHome) + sysproperty(key: "idea.system.path", value: systemPath) + sysproperty(key: "idea.config.path", value: configPath) + arg(value: "$classpathFile") + arg(line: "com.intellij.idea.Main traverseUI") + arg(value: targetFile) + + classpath() { + pathelement(location: "$javaRuntimeClasses") + } + } + + if (!new File(targetFile).exists()) { + buildContext.messages.error("Failed to build searchable options index: $targetFile doesn't exist") + } + }) + } + + File patchIdeaPropertiesFile() { + File originalFile = new File("$buildContext.paths.communityHome/bin/idea.properties") + + String text = originalFile.text + if (buildContext.productProperties.additionalIDEPropertiesFilePath != null) { + text += "\n" + new File(buildContext.productProperties.additionalIDEPropertiesFilePath).text + } + + //todo[nik] introduce special systemSelectorWithoutVersion instead? + String settingsDir = buildContext.systemSelector.replaceFirst("\\d+(\\.\\d+)?", "") + text = BuildUtils.replaceAll(text, ["settings_dir": settingsDir], "@@") + + text += (buildContext.applicationInfo.isEAP ? """ +#----------------------------------------------------------------------- +# Change to 'disabled' if you don't want to receive instant visual notifications +# about fatal errors that happen to an IDE or plugins installed. +#----------------------------------------------------------------------- +idea.fatal.error.notification=enabled +""" + : """ +#----------------------------------------------------------------------- +# Change to 'enabled' if you want to receive instant visual notifications +# about fatal errors that happen to an IDE or plugins installed. +#----------------------------------------------------------------------- +idea.fatal.error.notification=disabled +""") + File propertiesFile = new File(buildContext.paths.temp, "idea.properties") + propertiesFile.text = text + return propertiesFile + } + + @Override + File patchApplicationInfo() { + def sourceFile = buildContext.findApplicationInfoInSources() + def targetFile = new File(buildContext.paths.temp, sourceFile.name) + def date = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + BuildUtils.copyAndPatchFile(sourceFile.path, targetFile.path, + ["BUILD_NUMBER": buildContext.fullBuildNumber, "BUILD_DATE": date]) + return targetFile + } + + void layoutShared() { + new File(buildContext.paths.distAll, "build.txt").text = buildContext.fullBuildNumber + buildContext.ant.copy(todir: "$buildContext.paths.distAll/bin") { + fileset(dir: "$buildContext.paths.communityHome/bin") { + include(name: "*.*") + exclude(name: "idea.properties") + } + } + buildContext.ant.copy(todir: "$buildContext.paths.distAll/license") { + fileset(dir: "$buildContext.paths.communityHome/license") + } + + //todo[nik] these seems to be required for IDEA CE only + buildContext.ant.copy(todir: "$buildContext.paths.distAll") { + fileset(file: "$buildContext.paths.communityHome/LICENSE.txt") + fileset(file: "$buildContext.paths.communityHome/NOTICE.txt") + } + } + + @Override + void buildDistributions() { + layoutShared() + def propertiesFile = patchIdeaPropertiesFile() + + WindowsDistributionBuilder windowsBuilder = null + buildContext.executeStep("Build Windows distribution", BuildOptions.WINDOWS_DISTRIBUTION_STEP, { + windowsBuilder = new WindowsDistributionBuilder(buildContext) + windowsBuilder.layoutWin(propertiesFile) + }) + + LinuxDistributionBuilder linuxBuilder = null + buildContext.executeStep("Build Linux distribution", BuildOptions.LINUX_DISTRIBUTION_STEP) { + linuxBuilder = new LinuxDistributionBuilder(buildContext) + linuxBuilder.layoutUnix(propertiesFile) + } + + MacDistributionBuilderImpl macBuilder = null + buildContext.executeStep("Build Mac OS X distribution", BuildOptions.MAC_DISTRIBUTION_STEP) { + macBuilder = new MacDistributionBuilderImpl(buildContext) + macBuilder.layoutMac(propertiesFile) + } + + if (windowsBuilder != null && linuxBuilder != null && macBuilder != null) { + buildContext.executeStep("Build cross-platform distribution", BuildOptions.CROSS_PLATFORM_DISTRIBUTION_STEP) { + def crossPlatformBuilder = new CrossPlatformDistributionBuilder(buildContext) + crossPlatformBuilder.buildCrossPlatformZip(windowsBuilder.winDistPath, linuxBuilder.unixDistPath, macBuilder.macDistPath) + } + } + else { + buildContext.messages.info("Skipping building cross-platform distribution because some OS-specific distributions was skipeed") + } + } + + @Override + void cleanOutput() { + buildContext.messages.block("Clean output") { + def outputPath = buildContext.paths.buildOutputRoot + buildContext.messages.progress("Cleaning output directory $outputPath") + FileUtil.delete(new File(outputPath)) + } + } + + @Override + void compileProjectAndTests(List includingTestsInModules = []) { + if (buildContext.options.useCompiledClassesFromProjectOutput) { + buildContext.messages.info("Compilation skipped, the compiled classes from the project output will be used") + return + } + + buildContext.projectBuilder.cleanOutput() + buildContext.projectBuilder.buildProduction() + for (String moduleName : includingTestsInModules) { + buildContext.projectBuilder.makeModuleTests(buildContext.findModule(moduleName)) + } + } +} \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/impl/BuildUtils.groovy b/build/groovy/org/jetbrains/intellij/build/impl/BuildUtils.groovy new file mode 100644 index 000000000000..0ae16e45ee97 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/BuildUtils.groovy @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil +/** + * @author nik + */ +class BuildUtils { + static String replaceAll(String text, Map replacements, String marker = "__") { + replacements.each { + text = StringUtil.replace(text, "$marker$it.key$marker", it.value) + } + return text + } + + static void copyAndPatchFile(String sourcePath, String targetPath, Map replacements, String marker = "__") { + FileUtil.createParentDirs(new File(targetPath)) + new File(targetPath).text = replaceAll(new File(sourcePath).text, replacements, marker) + } +} \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/impl/CrossPlatformDistributionBuilder.groovy b/build/groovy/org/jetbrains/intellij/build/impl/CrossPlatformDistributionBuilder.groovy new file mode 100644 index 000000000000..f8dc2baeb329 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/CrossPlatformDistributionBuilder.groovy @@ -0,0 +1,110 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +import org.jetbrains.intellij.build.BuildContext +/** + * @author nik + */ +class CrossPlatformDistributionBuilder { + private final BuildContext buildContext + + CrossPlatformDistributionBuilder(BuildContext buildContext) { + this.buildContext = buildContext + } + + void buildCrossPlatformZip(String winDistPath, String linuxDistPath, String macDistPath) { + buildContext.messages.block("Building cross-platform zip") { + def executableName = buildContext.fileNamePrefix + def zipDir = "$buildContext.paths.temp/cross-platform-zip" + buildContext.ant.copy(todir: "$zipDir/bin/win") { + fileset(dir: "$linuxDistPath/bin") { + include(name: "idea.properties") + } + } + buildContext.ant.copy(todir: "$zipDir/bin/linux") { + fileset(dir: "$linuxDistPath/bin") { + include(name: "*.vmoptions") + include(name: "idea.properties") + } + } + buildContext.ant.copy(todir: "$zipDir/bin/mac") { + fileset(dir: "$macDistPath/bin") { + include(name: "${executableName}.vmoptions") + include(name: "idea.properties") + } + } + buildContext.ant.copy(file: "$macDistPath/bin/${executableName}.vmoptions", tofile: "$zipDir/bin/mac/${executableName}64.vmoptions") + buildContext.ant.copy(todir: "$zipDir/bin") { + fileset(dir: "$macDistPath/bin") { + include(name: "*.jnilib") + } + mapper(type: "glob", from: "*.jnilib", to: "*.dylib") + } + + String targetPath = "$buildContext.paths.artifacts/$buildContext.fileNamePrefix${buildContext.fullBuildNumber}.zip" + buildContext.ant.zip(zipfile: targetPath, duplicate: "fail") { + fileset(dir: buildContext.paths.distAll) { + exclude(name: "bin/idea.properties") + } + fileset(dir: zipDir) + + fileset(dir: winDistPath) { + exclude(name: "bin/fsnotifier*.exe") + exclude(name: "bin/*.exe.vmoptions") + exclude(name: "bin/${executableName}*.exe") + exclude(name: "bin/idea.properties") + } + zipfileset(dir: "$winDistPath/bin", prefix: "bin/win") { + include(name: "fsnotifier*.exe") + include(name: "*.exe.vmoptions") + } + + fileset(dir: linuxDistPath) { + exclude(name: "bin/fsnotifier*") + exclude(name: "bin/*.vmoptions") + exclude(name: "bin/*.sh") + exclude(name: "bin/idea.properties") + exclude(name: "help/**") + } + zipfileset(dir: "$linuxDistPath/bin", filemode: "775", prefix: "bin") { + include(name: "*.sh") + } + zipfileset(dir: "$linuxDistPath/bin", prefix: "bin/linux", filemode: "775") { + include(name: "fsnotifier*") + } + + fileset(dir: macDistPath) { + exclude(name: "bin/fsnotifier*") + exclude(name: "bin/restarter*") + exclude(name: "bin/*.sh") + exclude(name: "bin/*.py") + exclude(name: "bin/*.jnilib") + exclude(name: "bin/idea.properties") + exclude(name: "bin/*.vmoptions") + } + zipfileset(dir: "$macDistPath/bin", filemode: "775", prefix: "bin") { + include(name: "restarter*") + include(name: "*.py") + } + zipfileset(dir: "$macDistPath/bin", prefix: "bin/mac", filemode: "775") { + include(name: "fsnotifier*") + } + } + buildContext.notifyArtifactBuilt(targetPath) + } + } +} \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/impl/JdkUtils.groovy b/build/groovy/org/jetbrains/intellij/build/impl/JdkUtils.groovy new file mode 100644 index 000000000000..1e457e28fa71 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/JdkUtils.groovy @@ -0,0 +1,73 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +import com.intellij.openapi.util.SystemInfo +import com.intellij.util.SystemProperties +import org.jetbrains.intellij.build.BuildMessages +import org.jetbrains.jps.model.JpsGlobal +import org.jetbrains.jps.model.java.JdkVersionDetector +import org.jetbrains.jps.model.java.JpsJavaExtensionService +import org.jetbrains.jps.model.library.JpsOrderRootType + +/** + * @author nik + */ +class JdkUtils { + public static void defineJdk(JpsGlobal global, String jdkName, String jdkHomePath) { + def sdk = JpsJavaExtensionService.instance.addJavaSdk(global, jdkName, jdkHomePath) + def toolsJar = new File(jdkHomePath, "lib/tools.jar") + if (toolsJar.exists()) { + sdk.addRoot(toolsJar, JpsOrderRootType.COMPILED) + } + } + + public static String computeJdkHome(BuildMessages messages, String propertyName, String defaultDir, String envVarName) { + def jdkDir = System.getProperty(propertyName) + if (jdkDir != null) { + return jdkDir + } + + jdkDir = SystemInfo.isMac ? "$defaultDir/Home" : defaultDir + if (new File(jdkDir).exists()) { + messages.info("$propertyName set to $jdkDir") + } + else { + jdkDir = System.getenv(envVarName) + if (jdkDir != null) { + messages.info("'$defaultDir' doesn't exist, $propertyName set to '$envVarName' environment variable: $jdkDir") + } + else { + jdkDir = getCurrentJdk() + def version = JdkVersionDetector.instance.detectJdkVersion(jdkDir) + if (propertyName.contains("8") && !version.contains("1.8.")) { + messages.error("JDK 1.8 is required to compile the project, but '$propertyName' property and '$envVarName' environment variable aren't defined and default JDK $jdkDir ($version) cannot be used as JDK 1.8") + return null + } + messages.info("'$envVarName' isn't defined and '$defaultDir' doesn't exist, $propertyName set to $jdkDir") + } + } + return jdkDir + } + + private static String getCurrentJdk() { + def javaHome = SystemProperties.javaHome + if (new File(javaHome).name == "jre") { + return new File(javaHome).getParent() + } + return javaHome + } +} diff --git a/build/groovy/org/jetbrains/intellij/build/impl/JvmArchitecture.groovy b/build/groovy/org/jetbrains/intellij/build/impl/JvmArchitecture.groovy new file mode 100644 index 000000000000..5f95ba68e425 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/JvmArchitecture.groovy @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +/** + * @author nik + */ +enum JvmArchitecture { + x32(""), x64("64") + + final String fileSuffix + + JvmArchitecture(String fileSuffix) { + this.fileSuffix = fileSuffix + } +} diff --git a/build/groovy/org/jetbrains/intellij/build/impl/LinuxDistributionBuilder.groovy b/build/groovy/org/jetbrains/intellij/build/impl/LinuxDistributionBuilder.groovy new file mode 100644 index 000000000000..a39e2253ff84 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/LinuxDistributionBuilder.groovy @@ -0,0 +1,153 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +import org.jetbrains.intellij.build.BuildContext + +/** + * @author nik + */ +class LinuxDistributionBuilder { + private final BuildContext buildContext + final String unixDistPath + + LinuxDistributionBuilder(BuildContext buildContext) { + this.buildContext = buildContext + unixDistPath = "$buildContext.paths.buildOutputRoot/dist.unix" + } + + //todo[nik] rename + void layoutUnix(File ideaProperties) { + buildContext.ant.copy(todir: "$unixDistPath/bin") { + fileset(dir: "$buildContext.paths.communityHome/bin/linux") + } + buildContext.ant.copy(file: ideaProperties.path, todir: "$unixDistPath/bin") + //todo[nik] converting line separators to unix-style make sense only when building Linux distributions under Windows on a local machine; + // for real installers we need to checkout all text files with 'lf' separators anyway + buildContext.ant.fixcrlf(file: "$unixDistPath/bin/idea.properties", eol: "unix") + buildContext.ant.copy(file: buildContext.productProperties.icon128, tofile: "$unixDistPath/bin/${buildContext.fileNamePrefix}.png") + + unixScripts() + unixVMOptions() + unixReadme() + buildContext.productProperties.customLinLayout(unixDistPath) + buildTarGz(false) + if (new File(buildContext.paths.linuxJre).exists()) { + buildTarGz(true) + } + } + + private void unixScripts() { + String name = "${buildContext.fileNamePrefix}.sh" + String fullName = buildContext.applicationInfo.productName + String productUpperCase = buildContext.applicationInfo.shortProductName.toUpperCase() + String vmOptionsFileName = buildContext.fileNamePrefix + + String classPath = "CLASSPATH=\"\$IDE_HOME/lib/${buildContext.bootClassPathJarNames[0]}\"\n" + classPath += buildContext.bootClassPathJarNames[1..-1].collect { "CLASSPATH=\"\$CLASSPATH:\$IDE_HOME/lib/${it}\"" }.join("\n") + def jvmArgs = buildContext.productProperties.ideJvmArgs + if (buildContext.productProperties.toolsJarRequired) { + classPath += "\nCLASSPATH=\"\$CLASSPATH:\$JDK/lib/tools.jar\"" + jvmArgs = "$jvmArgs -Didea.jre.check=true".trim() + } + + buildContext.ant.copy(todir: "${unixDistPath}/bin") { + fileset(dir: "$buildContext.paths.communityHome/bin/scripts/unix") + + filterset(begintoken: "@@", endtoken: "@@") { + filter(token: "product_full", value: fullName) + filter(token: "product_uc", value: productUpperCase) + filter(token: "vm_options", value: vmOptionsFileName) + filter(token: "isEap", value: buildContext.applicationInfo.isEAP) + filter(token: "system_selector", value: buildContext.systemSelector) + filter(token: "ide_jvm_args", value: jvmArgs) + filter(token: "class_path", value: classPath) + filter(token: "script_name", value: name) + } + } + + if (name != "idea.sh") { + //todo[nik] rename idea.sh in sources to something more generic + buildContext.ant.move(file: "${unixDistPath}/bin/idea.sh", tofile: "${unixDistPath}/bin/$name") + } + String inspectScript = buildContext.productProperties.customInspectScriptName + if (inspectScript != null && inspectScript != "inspect") { + buildContext.ant.move(file: "${unixDistPath}/bin/inspect.sh", tofile: "${unixDistPath}/bin/${inspectScript}.sh") + } + + buildContext.ant.fixcrlf(srcdir: "${unixDistPath}/bin", includes: "*.sh", eol: "unix") + } + + private void unixVMOptions() { + JvmArchitecture.values().each { + def fileName = "${buildContext.fileNamePrefix}${it.fileSuffix}.vmoptions" + //todo[nik] why we don't add yourkit agent on unix? + def options = VmOptionsGenerator.computeVmOptions(it, buildContext.applicationInfo.isEAP, null) + " -Dawt.useSystemAAFontSettings=lcd" + new File(unixDistPath, "bin/$fileName").text = options.replace(' ', '\n') + } + } + + private void unixReadme() { + String fullName = buildContext.applicationInfo.productName + BuildUtils.copyAndPatchFile("$buildContext.paths.communityHome/build/Install-Linux-tar.txt", "$unixDistPath/Install-Linux-tar.txt", + ["product_full" : fullName, + "product" : buildContext.fileNamePrefix, + "system_selector": buildContext.systemSelector], "@@") + buildContext.ant.fixcrlf(file: "$unixDistPath/bin/Install-Linux-tar.txt", eol: "unix") + } + + private void buildTarGz(boolean bundleJre) { + def tarRoot = buildContext.productProperties.linuxAppRoot(buildContext.buildNumber) + def suffix = bundleJre ? "" : "-no-jdk" + def tarPath = "$buildContext.paths.artifacts/${buildContext.productProperties.archiveName(buildContext.buildNumber)}${suffix}.tar" + def extraBins = buildContext.productProperties.linux.extraLinuxBins + def paths = [buildContext.paths.distAll, unixDistPath] + if (bundleJre) { + paths += buildContext.paths.linuxJre + extraBins += "jre/jre/bin/*" + } + buildContext.messages.block("Build Linux tar.gz archive${bundleJre ? "" : " (without JRE)"}") { + buildContext.ant.tar(tarfile: tarPath, longfile: "gnu") { + paths.each { + tarfileset(dir: it, prefix: tarRoot) { + exclude(name: "bin/*.sh") + exclude(name: "bin/fsnotifier*") + extraBins.each { + exclude(name: it) + } + type(type: "file") + } + } + + paths.each { + tarfileset(dir: it, filemode: "755", prefix: tarRoot) { + include(name: "bin/*.sh") + include(name: "bin/fsnotifier*") + extraBins.each { + include(name: it) + } + type(type: "file") + } + } + } + + String gzPath = "${tarPath}.gz" + buildContext.ant.gzip(src: tarPath, zipfile: gzPath) + buildContext.ant.delete(file: tarPath) + buildContext.notifyArtifactBuilt(gzPath) + } + } +} \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/impl/MacDistributionBuilderImpl.groovy b/build/groovy/org/jetbrains/intellij/build/impl/MacDistributionBuilderImpl.groovy new file mode 100644 index 000000000000..1235902aa7db --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/MacDistributionBuilderImpl.groovy @@ -0,0 +1,305 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +import org.jetbrains.intellij.build.BuildContext + +import java.time.LocalDate +/** + * @author nik + */ +class MacDistributionBuilderImpl { + private final BuildContext buildContext + final String macDistPath + + MacDistributionBuilderImpl(BuildContext buildContext) { + this.buildContext = buildContext + macDistPath = "$buildContext.paths.buildOutputRoot/dist.mac" + } + + public layoutMac(File ideaPropertiesFile) { + def docTypes = buildContext.productProperties.mac.docTypes ?: """ + + CFBundleTypeExtensions + + ipr + + CFBundleTypeIconFile + ${buildContext.fileNamePrefix}.icns + CFBundleTypeName + ${buildContext.applicationInfo.productName} Project File + CFBundleTypeRole + Editor + +""" + Map customIdeaProperties = ["idea.jre.check": "$buildContext.productProperties.toolsJarRequired"]; + layoutMacApp(ideaPropertiesFile, customIdeaProperties, docTypes) + buildContext.productProperties.customMacLayout(macDistPath) + buildMacZip() + } + + private void layoutMacApp(File ideaPropertiesFile, Map customIdeaProperties, String docTypes) { + String target = macDistPath + def macProductProperties = buildContext.productProperties.mac + buildContext.ant.copy(todir: "$target/bin") { + fileset(dir: "$buildContext.paths.communityHome/bin/mac") + } + + buildContext.ant.copy(todir: target) { + fileset(dir: "$buildContext.paths.communityHome/build/conf/mac/Contents") + } + + String executable = buildContext.fileNamePrefix + String icns = "idea.icns" //todo[nik] rename to more generic name? + String helpId = macProductProperties.helpId + String helpIcns = "$target/Resources/${helpId}.help/Contents/Resources/Shared/product.icns" + String customIcns = buildContext.productProperties.icns + if (customIcns != null) { + buildContext.ant.delete(file: "$target/Resources/idea.icns") + buildContext.ant.copy(file: customIcns, todir: "$target/Resources") + buildContext.ant.copy(file: customIcns, tofile: helpIcns) + icns = new File(customIcns).name + } + else { + buildContext.ant.copy(file: "$target/Resources/idea.icns", tofile: helpIcns) + } + + String fullName = buildContext.applicationInfo.productName + + //todo[nik] why do we put vm options to separate places (some into Info.plist, some into vmoptions file)? + String vmOptions = "-Dfile.encoding=UTF-8 ${VmOptionsGenerator.computeCommonVmOptions(buildContext.applicationInfo.isEAP)} -Xverify:none" + + //todo[nik] improve + String minor = buildContext.applicationInfo.minorVersion + boolean isNotRelease = buildContext.applicationInfo.isEAP && !minor.contains("RC") && !minor.contains("Beta") + String version = isNotRelease ? "EAP $buildContext.fullBuildNumber" : "${buildContext.applicationInfo.majorVersion}.${minor}" + String EAP = isNotRelease ? "-EAP" : "" + + //todo[nik] don't mix properties for idea.properties file with properties for Info.plist + Map properties = readIdeaProperties(ideaPropertiesFile, customIdeaProperties) + + def coreKeys = ["idea.platform.prefix", "idea.paths.selector", "idea.executable"] + + String coreProperties = submapToXml(properties, coreKeys); + + StringBuilder effectiveProperties = new StringBuilder() + properties.each { k, v -> + if (!coreKeys.contains(k)) { + effectiveProperties.append("$k=$v\n"); + } + } + + new File("$target/bin/idea.properties").text = effectiveProperties.toString() + String ideaVmOptions = "${VmOptionsGenerator.vmOptionsForArch(JvmArchitecture.x64)} -XX:+UseCompressedOops" + if (buildContext.applicationInfo.isEAP && buildContext.productProperties.includeYourkitAgentInEAP && macProductProperties.includeYourkitAgentInEAP) { + ideaVmOptions += VmOptionsGenerator.yourkitOptions(buildContext.systemSelector, "") + } + new File("$target/bin/${executable}.vmoptions").text = ideaVmOptions.split(" ").join("\n") + + String classPath = buildContext.bootClassPathJarNames.collect { "\$APP_PACKAGE/Contents/lib/${it}" }.join(":") + + String archsString = """ + LSArchitecturePriority + """ + macProductProperties.architectures.each { + archsString += "$it" + } + archsString += "\n" + + List urlSchemes = macProductProperties.urlSchemes + String urlSchemesString = "" + if (urlSchemes.size() > 0) { + urlSchemesString += """ + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + Stacktrace + CFBundleURLSchemes + +""" + urlSchemes.each { scheme -> + urlSchemesString += " ${scheme}" + } + urlSchemesString += """ + + + +""" + } + + String todayYear = LocalDate.now().year + buildContext.ant.replace(file: "$target/Info.plist") { + replacefilter(token: "@@build@@", value: buildContext.fullBuildNumber) + replacefilter(token: "@@doc_types@@", value: docTypes ?: "") + replacefilter(token: "@@executable@@", value: executable) + replacefilter(token: "@@icns@@", value: icns) + replacefilter(token: "@@bundle_name@@", value: fullName) + replacefilter(token: "@@product_state@@", value: EAP) + replacefilter(token: "@@bundle_identifier@@", value: macProductProperties.bundleIdentifier) + replacefilter(token: "@@year@@", value: "$todayYear") + replacefilter(token: "@@company_name@@", value: buildContext.applicationInfo.companyName) + replacefilter(token: "@@min_year@@", value: "2000") + replacefilter(token: "@@max_year@@", value: "$todayYear") + replacefilter(token: "@@version@@", value: version) + replacefilter(token: "@@vmoptions@@", value: vmOptions) + replacefilter(token: "@@idea_properties@@", value: coreProperties) + replacefilter(token: "@@class_path@@", value: classPath) + replacefilter(token: "@@help_id@@", value: helpId) + replacefilter(token: "@@url_schemes@@", value: urlSchemesString) + replacefilter(token: "@@archs@@", value: archsString) + replacefilter(token: "@@min_osx@@", value: macProductProperties.minOSXVersion) + } + + if (executable != "idea") { + buildContext.ant.move(file: "$target/MacOS/idea", tofile: "$target/MacOS/$executable") + } + + buildContext.ant.replace(file: "$target/bin/inspect.sh") { + replacefilter(token: "@@product_full@@", value: fullName) + replacefilter(token: "@@script_name@@", value: executable) + } + String inspectScript = buildContext.productProperties.customInspectScriptName + if (inspectScript != null && inspectScript != "inspect") { + buildContext.ant.move(file: "$target/bin/inspect.sh", tofile: "$target/bin/${inspectScript}.sh") + } + + buildContext.ant.fixcrlf(srcdir: "$target/bin", includes: "*.sh", eol: "unix") + buildContext.ant.fixcrlf(srcdir: "$target/bin", includes: "*.py", eol: "unix") + } + + void buildMacZip() { + buildContext.messages.block("Build zip archive for Mac OS") { + def extraBins = buildContext.productProperties.mac.extraMacBins + def allPaths = [buildContext.paths.distAll, macDistPath] + def zipRoot = buildContext.productProperties.macAppRoot(buildContext.applicationInfo, buildContext.buildNumber) + def targetPath = "$buildContext.paths.artifacts/${buildContext.productProperties.archiveName(buildContext.buildNumber)}.mac.zip" + buildContext.ant.zip(zipfile: targetPath) { + allPaths.each { + zipfileset(dir: it, prefix: zipRoot) { + exclude(name: "bin/*.sh") + exclude(name: "bin/*.py") + exclude(name: "bin/fsnotifier") + exclude(name: "bin/restarter") + exclude(name: "MacOS/*") + exclude(name: "build.txt") + exclude(name: "NOTICE.txt") + extraBins.each { + exclude(name: it) + } + exclude(name: "bin/idea.properties") + } + } + + allPaths.each { + zipfileset(dir: it, filemode: "755", prefix: zipRoot) { + include(name: "bin/*.sh") + include(name: "bin/*.py") + include(name: "bin/fsnotifier") + include(name: "bin/restarter") + include(name: "MacOS/*") + extraBins.each { + include(name: it) + } + } + } + + allPaths.each { + zipfileset(dir: it, prefix: "$zipRoot/Resources") { + include(name: "build.txt") + include(name: "NOTICE.txt") + } + } + + zipfileset(file: "$macDistPath/bin/idea.properties", prefix: "$zipRoot/bin") + } + buildContext.notifyArtifactBuilt(targetPath) + } + } + + private static String submapToXml(Map properties, List keys) { +// generate properties description for Info.plist + StringBuilder buff = new StringBuilder() + + keys.each { key -> + String value = properties[key] + if (value != null) { + String string = + """ + $key + $value +""" + buff.append(string) + } + } + return buff.toString() + } + + /** + * E.g. + * + * Load all properties from file: + * readIdeaProperties(buildContext, "$home/ruby/build/idea.properties") + * + * Load all properties except "idea.cycle.buffer.size", change "idea.max.intellisense.filesize" to 3000 + * and enable "idea.is.internal" mode: + * readIdeaProperties(buildContext, "$home/ruby/build/idea.properties", + * "idea.properties" : ["idea.max.intellisense.filesize" : 3000, + * "idea.cycle.buffer.size" : null, + * "idea.is.internal" : true ]) + * @param args + * @return text xml properties description in xml + */ + private Map readIdeaProperties(File propertiesFile, Map customProperties = [:]) { + Map ideaProperties = [:] + propertiesFile.withReader { + Properties loadedProperties = new Properties(); + loadedProperties.load(it) + ideaProperties.putAll(loadedProperties as Map) + } + + Map properties = + ["CVS_PASSFILE" : "~/.cvspass", + "com.apple.mrj.application.live-resize" : "false", + "idea.paths.selector" : buildContext.systemSelector, + "idea.executable" : buildContext.fileNamePrefix, + "java.endorsed.dirs" : "", + "idea.smooth.progress" : "false", + "apple.laf.useScreenMenuBar" : "true", + "apple.awt.graphics.UseQuartz" : "true", + "apple.awt.fullscreencapturealldisplays": "false"] + if (buildContext.productProperties.platformPrefix != null) { + properties["idea.platform.prefix"] = buildContext.productProperties.platformPrefix + } + + properties += customProperties + + properties.each { k, v -> + if (v == null) { + // if overridden with null - ignore property + ideaProperties.remove(k) + } + else { + // if property is overridden in args map - use new value + ideaProperties[k] = v + } + } + + return ideaProperties + } +} \ No newline at end of file diff --git a/build/groovy/org/jetbrains/intellij/build/impl/VmOptionsGenerator.groovy b/build/groovy/org/jetbrains/intellij/build/impl/VmOptionsGenerator.groovy new file mode 100644 index 000000000000..cfff43a2285f --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/VmOptionsGenerator.groovy @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +/** + * @author nik + */ +class VmOptionsGenerator { + private static final String COMMON_VM_OPTIONS = "-XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=50 -ea " + + "-Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true " + + "-XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow" + + static String computeVmOptions(JvmArchitecture arch, boolean isEAP, String yourkitSessionName = null) { + String options = vmOptionsForArch(arch) + " " + computeCommonVmOptions(isEAP) + if (yourkitSessionName != null) { + options += " " + yourkitOptions(yourkitSessionName, arch.fileSuffix) + } + return options + } + + static String computeCommonVmOptions(boolean isEAP) { + String options = COMMON_VM_OPTIONS + if (isEAP) { + options += " -XX:MaxJavaStackTraceDepth=-1" + } + return options + } + + static String vmOptionsForArch(JvmArchitecture arch) { + switch (arch) { + case JvmArchitecture.x32: return "-server -Xms128m -Xmx512m -XX:ReservedCodeCacheSize=240m" + case JvmArchitecture.x64: return "-Xms128m -Xmx750m -XX:ReservedCodeCacheSize=240m" + } + throw new AssertionError(arch) + } + + static String yourkitOptions(String sessionName, String fileSuffix) { + "-agentlib:yjpagent$fileSuffix=probe_disable=*,disablealloc,disabletracing,onlylocal,disableexceptiontelemetry,delay=10000,sessionname=$sessionName".trim() + } +} + diff --git a/build/groovy/org/jetbrains/intellij/build/impl/WindowsDistributionBuilder.groovy b/build/groovy/org/jetbrains/intellij/build/impl/WindowsDistributionBuilder.groovy new file mode 100644 index 000000000000..0c4d4bcf556a --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/impl/WindowsDistributionBuilder.groovy @@ -0,0 +1,158 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.intellij.build.impl + +import org.jetbrains.intellij.build.BuildContext +import org.jetbrains.jps.model.module.JpsModuleSourceRoot +/** + * @author nik + */ +class WindowsDistributionBuilder { + private final BuildContext buildContext + final String winDistPath + + WindowsDistributionBuilder(BuildContext buildContext) { + this.buildContext = buildContext + winDistPath = "$buildContext.paths.buildOutputRoot/dist.win" + } + + //todo[nik] rename + void layoutWin(File ideaProperties) { + buildContext.ant.copy(todir: "$winDistPath/bin") { + fileset(dir: "$buildContext.paths.communityHome/bin/win") + } + buildContext.ant.copy(file: ideaProperties.path, todir: "$winDistPath/bin") + buildContext.ant.fixcrlf(file: "$winDistPath/bin/idea.properties", eol: "dos") + + buildContext.ant.copy(file: buildContext.productProperties.ico, tofile: "$winDistPath/bin/${buildContext.fileNamePrefix}.ico") + if (buildContext.productProperties.windows.includeBatchLauncher) { + winScripts() + } + winVMOptions() + buildWinLauncher(JvmArchitecture.x32) + buildWinLauncher(JvmArchitecture.x64) + buildContext.productProperties.customWinLayout(winDistPath) + buildWinZip() + } + + //todo[nik] rename + private void winScripts() { + String fullName = buildContext.applicationInfo.productName + String productUpperCase = buildContext.applicationInfo.shortProductName.toUpperCase() + //todo[nik] looks like names without .exe were also supported, do we need this? + String vmOptionsFileName = "${buildContext.fileNamePrefix}%BITS%.exe" + + String classPath = "SET CLASS_PATH=%IDE_HOME%\\lib\\${buildContext.bootClassPathJarNames[0]}\n" + classPath += buildContext.bootClassPathJarNames[1..-1].collect { "SET CLASS_PATH=%CLASS_PATH%;%IDE_HOME%\\lib\\$it" }.join("\n") + def jvmArgs = buildContext.productProperties.ideJvmArgs + if (buildContext.productProperties.toolsJarRequired) { + classPath += "\nSET CLASS_PATH=%CLASS_PATH%;%JDK%\\lib\\tools.jar" + jvmArgs = "$jvmArgs -Didea.jre.check=true".trim() + } + + def batName = "${buildContext.fileNamePrefix}.bat" + buildContext.ant.copy(todir: "$winDistPath/bin") { + fileset(dir: "$buildContext.paths.communityHome/bin/scripts/win") + + filterset(begintoken: "@@", endtoken: "@@") { + filter(token: "product_full", value: fullName) + filter(token: "product_uc", value: productUpperCase) + filter(token: "vm_options", value: vmOptionsFileName) + filter(token: "isEap", value: buildContext.applicationInfo.isEAP) + filter(token: "system_selector", value: buildContext.systemSelector) + filter(token: "ide_jvm_args", value: jvmArgs) + filter(token: "class_path", value: classPath) + filter(token: "script_name", value: batName) + } + } + + if (batName != "idea.bat") { + //todo[nik] rename idea.bat in sources to something more generic + buildContext.ant.move(file: "$winDistPath/bin/idea.bat", tofile: "$winDistPath/bin/$batName") + } + String inspectScript = buildContext.productProperties.customInspectScriptName + if (inspectScript != null && inspectScript != "inspect") { + buildContext.ant.move(file: "$winDistPath/bin/inspect.bat", tofile: "$winDistPath/bin/${inspectScript}.bat") + } + + + buildContext.ant.fixcrlf(srcdir: "$winDistPath/bin", includes: "*.bat", eol: "dos") + } + + //todo[nik] rename + private void winVMOptions() { + JvmArchitecture.values().each { + def yourkitSessionName = buildContext.applicationInfo.isEAP && buildContext.productProperties.includeYourkitAgentInEAP ? buildContext.systemSelector : null + def fileName = "${buildContext.fileNamePrefix}${it.fileSuffix}.exe.vmoptions" + new File(winDistPath, "bin/$fileName").text = VmOptionsGenerator.computeVmOptions(it, buildContext.applicationInfo.isEAP, yourkitSessionName).replace(' ', '\n') + } + + buildContext.ant.fixcrlf(srcdir: "$winDistPath/bin", includes: "*.vmoptions", eol: "dos") + } + + private void buildWinLauncher(JvmArchitecture arch) { + buildContext.messages.block("Build Windows executable ${arch.name()}") { + String exeFileName = "$buildContext.fileNamePrefix${arch.fileSuffix}.exe" + def launcherPropertiesPath = "${buildContext.paths.temp}/launcher.properties" + //todo[nik] generate launcher.properties file automatically + def launcherPropertiesTemplatePath = arch == JvmArchitecture.x32 ? buildContext.productProperties.exe_launcher_properties + : buildContext.productProperties.exe64_launcher_properties + + BuildUtils.copyAndPatchFile(launcherPropertiesTemplatePath, launcherPropertiesPath, + ["PRODUCT_PATHS_SELECTOR": buildContext.systemSelector, + "IDE-NAME": buildContext.applicationInfo.shortProductName.toUpperCase()]) + + def communityHome = "$buildContext.paths.communityHome" + String inputPath = "$communityHome/bin/WinLauncher/WinLauncher${arch.fileSuffix}.exe" + buildContext.ant.java(classname: "com.pme.launcher.LauncherGeneratorMain", fork: "true", failonerror: "true") { + sysproperty(key: "java.awt.headless", value: "true") + arg(value: inputPath) + arg(value: buildContext.findApplicationInfoInSources().absolutePath) + arg(value: "$communityHome/native/WinLauncher/WinLauncher/resource.h") + arg(value: launcherPropertiesPath) + arg(value: "$winDistPath/bin/$exeFileName") + classpath { + pathelement(location: "$communityHome/build/lib/launcher-generator.jar") + fileset(dir: "$communityHome/lib") { + include(name: "guava*.jar") + include(name: "jdom.jar") + include(name: "sanselan*.jar") + } + [buildContext.findApplicationInfoModule(), buildContext.findModule("icons")].collectMany { it.sourceRoots }.each { JpsModuleSourceRoot root -> + pathelement(location: root.file.absolutePath) + } + } + } + } + } + + private void buildWinZip() { + buildContext.messages.block("Build Windows .zip distribution") { + def targetPath = "$buildContext.paths.artifacts/${buildContext.productProperties.archiveName(buildContext.buildNumber)}.win.zip" + def zipPrefix = buildContext.productProperties.winAppRoot(buildContext.buildNumber) + def dirs = [buildContext.paths.distAll, winDistPath] + if (buildContext.productProperties.windows.bundleJre && new File(buildContext.paths.winJre).exists()) { + dirs += buildContext.paths.winJre + } + buildContext.ant.zip(zipfile: targetPath) { + dirs.each { + zipfileset(dir: it, prefix: zipPrefix) + } + } + buildContext.notifyArtifactBuilt(targetPath) + } + } +} \ No newline at end of file diff --git a/build/scripts/dist.gant b/build/scripts/dist.gant index 863055658d37..432108e6dd67 100644 --- a/build/scripts/dist.gant +++ b/build/scripts/dist.gant @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - -import com.intellij.openapi.util.text.StringUtil import org.apache.tools.ant.BuildException +import org.jetbrains.intellij.build.BuildContext +import org.jetbrains.intellij.build.BuildOptions +import org.jetbrains.intellij.build.BuildTasks import org.jetbrains.jps.gant.LayoutInfo import static org.jetbrains.jps.idea.IdeaProjectLoader.guessHome @@ -27,40 +27,12 @@ requireProperty("out", "$home/out") // "out" has to be canonical, otherwise the ant build fails // with mysterious errors -out = new File(out).getCanonicalPath() -setProperty("out", out) +String out = new File(out).getCanonicalPath() -class Paths { - final sandbox - final distWin - final distAll - final distUnix - final distMac - final artifacts - final artifacts_core - final artifacts_jps - final ideaSystem - final ideaConfig +loadProductProperties(home) - def Paths(String out) { - sandbox = out - distWin = "$sandbox/dist.win.ce" - distAll = "$sandbox/dist.all.ce" - distUnix = "$sandbox/dist.unix.ce" - distMac = "$sandbox/dist.mac.ce" - artifacts = "$sandbox/artifacts" - artifacts_core = "$artifacts/core" - artifacts_jps = "$artifacts/jps" - ideaSystem = "$sandbox/system" - ideaConfig = "$sandbox/config" - } -} - -def paths = new Paths(out) -setProperty("paths", paths) -loadProductProperties(home, snapshot) - -def loadProductProperties(String home, String buildNumber) { +def loadProductProperties(String home) { + //todo[nik] improve (load implementation class from groovy resources root directly?) requireProperty("product", "idea") requireProperty("productPropertiesPath", "") if (productPropertiesPath.isEmpty()) { @@ -69,231 +41,75 @@ def loadProductProperties(String home, String buildNumber) { if (!new File(productPropertiesPath).exists()) { throw new BuildException("No product specific properties file found at: " + productPropertiesPath) } - setProperty("productProperties", includeFile(productPropertiesPath).getProperties(home, buildNumber)) - productProperties.ideJvmArgs = productProperties.ideJvmArgs != null ? - productProperties.ideJvmArgs + " -Didea.jre.check=true" : - "-Didea.jre.check=true" + setProperty("productProperties", includeFile(productPropertiesPath).getProperties(home)) +} + +private BuildContext createBuildContext(String out, BuildOptions options = new BuildOptions()) { +//todo[nik] construct buildOutputRoot automatically based on product name + BuildContext.createContext(ant, projectBuilder, project, global, home, home, "$out/release", productProperties, options) +} + +private static void compileModules(BuildContext buildContext) { + BuildTasks.create(buildContext).compileProjectAndTests(["jps-builders"]) } target(compile: "Compile project") { - projectBuilder.stage("Cleaning up sandbox folder") - forceDelete(paths.sandbox) - - loadProject() - - [paths.sandbox, paths.distWin, paths.distAll, paths.distUnix, paths.distMac, paths.artifacts, paths.artifacts_core, paths.artifacts_jps].each { - ant.mkdir(dir: it) - } - - projectBuilder.targetFolder = "$out/classes" - clearBuildCaches() - projectBuilder.cleanOutput() - projectBuilder.buildProduction() - projectBuilder.makeModuleTests(findModule("jps-builders")) -} - -private String appInfoFile() { - return productProperties.appInfoFile() + compileModules(createBuildContext(out)) } target('default': 'The default target') { - depends([compile]) - - // load ApplicationInfo.xml properties - ant.xmlproperty(file: appInfoFile(), collapseAttributes: "true") - - zipSources(home, paths.artifacts) - - indexSearchableOptions() - - layoutAll([buildNumber: productProperties.fullBuildNumber(), - system_selector: productProperties.systemSelector(), - platform_prefix: productProperties.platformPrefix, - ide_jvm_args: productProperties.ideJvmArgs, - tools_jar: true], - home, null, paths, true) - String archiveName = productProperties.archiveName() - String macZip = "$paths.artifacts/${archiveName}.mac.zip" - notifyArtifactBuilt(macZip) + def buildContext = createBuildContext(out) + compileModules(buildContext) + def tasks = BuildTasks.create(buildContext) + tasks.cleanOutput() + tasks.buildSearchableOptions("resources-en", ["community-main"], []) + layoutAll(buildContext, true) if (productProperties.buildUpdater) { // Generate updater.jar from the updater module (patch updater) layoutUpdater(out) } + tasks.zipSources() } +//todo[nik] do we really need this target? updates.xml calls layout.gant directly target('build-dist-jars' : 'Target to build jars from locally compiled classes') { - loadProject() - - // load ApplicationInfo.xml properties - ant.xmlproperty(file: appInfoFile(), collapseAttributes: "true") - - indexSearchableOptions() - - layoutAll([buildNumber: productProperties.fullBuildNumber(), - system_selector: productProperties.systemSelector(), - platform_prefix: productProperties.platformPrefix, - ide_jvm_args: productProperties.ideJvmArgs, - tools_jar: true], - home, null, paths) - String macZipPrefix = productProperties.prefix + productProperties.fullBuildNumber() - String macZip = "$paths.artifacts/${macZipPrefix}.mac.zip" - notifyArtifactBuilt(macZip) + def options = new BuildOptions() + options.useCompiledClassesFromProjectOutput = true + def buildContext = createBuildContext(out, options) + compileModules(buildContext) + def tasks = BuildTasks.create(buildContext) + tasks.cleanOutput() + tasks.buildSearchableOptions("resources-en", ["community-main"], []) + layoutAll(buildContext) } -private void indexSearchableOptions() { - buildSearchableOptions("${projectBuilder.moduleOutput(findModule("resources-en"))}/search", [], { - ant.pathelement(location: "$jdkHome/lib/tools.jar") - ant.pathelement(location: "$home/lib/junit.jar") - projectBuilder.moduleRuntimeClasspath(findModule("community-main"), false).each { - ant.pathelement(location: it) +def layoutAll(BuildContext buildContext, buildJps = false) { + def layouts = includeFile("$buildContext.paths.communityHome/build/scripts/layouts.gant") + def tasks = BuildTasks.create(buildContext) + def applicationInfo = tasks.patchApplicationInfo() + LayoutInfo info = layouts.layoutFull(buildContext.paths.projectHome, buildContext.paths.distAll, applicationInfo) + + buildContext.messages.block("Build intellij-core") { + String coreArtifactDir = "$buildContext.paths.artifacts/core" + buildContext.ant.mkdir(dir: coreArtifactDir) + layouts.layout_core(buildContext.paths.projectHome, coreArtifactDir) + buildContext.notifyArtifactBuilt(coreArtifactDir) + + def intellijCoreZip = "${buildContext.paths.artifacts}/intellij-core-${buildContext.buildNumber}.zip" + ant.zip(destfile: intellijCoreZip) { + fileset(dir: coreArtifactDir) } - }) -} - -def layoutAll(Map args, String home, String out, Paths _paths = null, buildJps = false) { - Paths paths = _paths != null ? _paths : new Paths(out) - - wireBuildDate(args.buildNumber, appInfoFile()) - ant.echo(message: args.buildNumber, file: "$paths.distAll/build.txt") - - def layouts = includeFile("$home/build/scripts/layouts.gant") - LayoutInfo info = layouts.layoutFull(home, paths.distAll, null) - layouts.layout_core(home, paths.artifacts_core) - ant.zip(destfile: "${paths.artifacts}/intellij-core-${StringUtil.trimStart(args.buildNumber, "IC-")}.zip") { - fileset(dir: paths.artifacts_core) + buildContext.notifyArtifactBuilt(intellijCoreZip) } - - notifyArtifactBuilt(paths.artifacts_core) if (buildJps) { - layouts.layoutJps(home, paths.artifacts_jps, args.buildNumber, {}) - notifyArtifactBuilt(paths.artifacts_jps) - } - - layout(paths.distAll) { - dir("bin") { - fileset(dir: "${home}/bin") { - include(name: "*.*") - } + buildContext.messages.block("Build standalone JPS") { + String jpsArtifactDir = "$buildContext.paths.artifacts/jps" + layouts.layoutJps(buildContext.paths.communityHome, jpsArtifactDir, buildContext.fullBuildNumber, {}) + buildContext.notifyArtifactBuilt(jpsArtifactDir) } - - dir("license") { - fileset(dir: "${home}/license") { - exclude(name: "placeholder.txt") - } - } - fileset(file: "${home}/LICENSE.txt") - fileset(file: "${home}/NOTICE.txt") - } - patchPropertiesFile(paths.distAll, args + [appendices: ["$home/build/conf/ideaCE.properties"]]) - - layoutWin(args, home, paths) - layoutMac(args, home, paths) - layoutUnix(args, home, paths) - - def macAppRoot = productProperties.macAppRoot() - def winAppRoot = productProperties.winAppRoot() - def linuxAppRoot = productProperties.linuxAppRoot() - def archiveName = productProperties.archiveName() - - List pathsDist = new File("${paths.sandbox}/jdk.win/jre").exists() ? [paths.distAll, paths.distWin, "${paths.sandbox}/jdk.win"] : [paths.distAll, paths.distWin] - buildWinZip("$paths.artifacts/${archiveName}.win.zip", pathsDist, winAppRoot) - //win oracle jdk - if (new File("${paths.sandbox}/jdk.oracle.win/jre").exists()) { - buildWinZip("$paths.artifacts/${archiveName}-oracle-win.zip", - [paths.distAll, paths.distWin, "${paths.sandbox}/jdk.oracle.win"]) } - - buildCrossPlatformZip("$paths.artifacts/idea${args.buildNumber}.zip", "${paths.sandbox}/sandbox-ce", [paths.distAll], - paths.distWin, paths.distUnix, paths.distMac) - - String macZip = "$paths.artifacts/${archiveName}.mac.zip" - buildMacZip(macAppRoot, macZip, [paths.distAll], paths.distMac, productProperties.extraMacBins) - - buildTarGz(linuxAppRoot, "$paths.artifacts/${archiveName}-no-jdk.tar", [paths.distAll, paths.distUnix], productProperties.extraLinuxBins) - requireProperty("jdk.linux", "false") - if (p("jdk.linux") != "false") { - buildTarGz(linuxAppRoot, "$paths.artifacts/${archiveName}.tar", [paths.distAll, paths.distUnix, "${paths.sandbox}/jdk.linux"], ["jre/jre/bin/*"] + productProperties.extraLinuxBins) - } + tasks.buildDistributions() return info } - -private layoutWin(Map args, String home, Paths paths) { - String target = paths.distWin - - layout(target) { - dir("bin") { - fileset(dir: "$home/bin/win") - } - } - - def name = productProperties.prefix - def ico = productProperties.ico - - ant.copy(file: ico, tofile: "$target/bin/${name}.ico") - - if (productProperties.includeBatchLauncher) { - winScripts(target, home, "${name}.bat", args) - } - winVMOptions(target, productProperties.includeYourkitAgentInEAP ? args.system_selector : null, "${name}.exe", "${name}64.exe") - def appInfoModulePath = productProperties.appInfoModulePath - - List resourcePaths = ["$home/${appInfoModulePath}/src", "$home/platform/icons/src"] - buildWinLauncher(home, "$home/bin/WinLauncher/WinLauncher.exe", "$target/bin/${name}.exe", appInfoFile(), - productProperties.exe_launcher_properties, args.system_selector, resourcePaths) - buildWinLauncher(home, "$home/bin/WinLauncher/WinLauncher64.exe", "$target/bin/${name}64.exe", appInfoFile(), - productProperties.exe64_launcher_properties, args.system_selector, resourcePaths) - - productProperties.customWinLayout(target) -} - -private layoutMac(Map _args, String home, Paths paths) { - String target = paths.distMac - def prefix = productProperties.prefix - - Map args = new HashMap(_args) - args.bundleIdentifier = productProperties.bundleIdentifier - args.doc_types = """ - - CFBundleTypeExtensions - - ipr - - CFBundleTypeIconFile - ${prefix}.icns - CFBundleTypeName - IntelliJ IDEA Project File - CFBundleTypeRole - Editor - -""" - args."idea.properties.path" = "${paths.distAll}/bin/idea.properties" - args."idea.properties" = ["idea.jre.check": true]; - args.urlSchemes = ["idea"] - args.mac_no_yjp = true - args.executable = prefix - args.icns = productProperties.icns - layoutMacApp(target, home, args) - productProperties.customMacLayout(target) -} - -private layoutUnix(Map args, String home, Paths paths) { - String target = paths.distUnix - - layout(target) { - dir("bin") { - fileset(dir: "$home/bin/linux") - } - } - - def name = productProperties.prefix - def icon128 = productProperties.icon128 - - ant.copy(file: icon128, tofile: "$target/bin/${name}.png") - - unixScripts(target, home, "${name}.sh", args) - unixVMOptions(target, "${name}") - unixReadme(target, home, args) - productProperties.customLinLayout(target) -} diff --git a/build/scripts/idea_properties.gant b/build/scripts/idea_properties.gant index 3d155b8efe49..89e9ed851833 100644 --- a/build/scripts/idea_properties.gant +++ b/build/scripts/idea_properties.gant @@ -1,5 +1,5 @@ +import org.jetbrains.intellij.build.ApplicationInfoProperties import org.jetbrains.intellij.build.ProductProperties - /* * Copyright (C) 2015 The Android Open Source Project * @@ -17,41 +17,47 @@ import org.jetbrains.intellij.build.ProductProperties */ -def getProperties(String home, String buildNumber) { +def getProperties(String home) { return new ProductProperties() { { prefix = "idea" + platformPrefix = "Idea" code = "IC" appInfoModule = "community-resources" - appInfoModulePath = "community-resources" + additionalIDEPropertiesFilePath = "$home/build/conf/ideaCE.properties" exe_launcher_properties = "$home/build/conf/ideaCE-launcher.properties" exe64_launcher_properties = "$home/build/conf/ideaCE64-launcher.properties" - bundleIdentifier = "com.jetbrains.intellij.ce" maySkipAndroidPlugin = true relativeAndroidHome = "android" relativeAndroidToolsBaseHome = "android/tools-base" + toolsJarRequired = true icon128 = "$home/platform/icons/src/icon_CE_128.png" ico = "$home/platform/icons/src/idea_CE.ico" + + windows.bundleJre = true + + mac.helpId = "IJ" + mac.urlSchemes = ["idea"] + mac.includeYourkitAgentInEAP = false + mac.bundleIdentifier = "com.jetbrains.intellij.ce" } def String appInfoFile() { "${projectBuilder.moduleOutput(findModule("community-resources"))}/idea/IdeaApplicationInfo.xml" } - def String fullBuildNumber() { "IC-$buildNumber" } + def String systemSelector(ApplicationInfoProperties applicationInfo) { "IdeaIC$applicationInfo.majorVersion" } - def String systemSelector() { "IdeaIC${p("component.version.major")}" } - - def String macAppRoot() { - isEap() ? "IntelliJ IDEA ${p("component.version.major")}.${p("component.version.minor")} CE EAP.app/Contents" + def String macAppRoot(ApplicationInfoProperties applicationInfo, String buildNumber) { + applicationInfo.isEAP ? "IntelliJ IDEA ${applicationInfo.majorVersion}.${applicationInfo.minorVersion} CE EAP.app/Contents" : "IntelliJ IDEA CE.app/Contents" } - def String winAppRoot() { "" } + def String winAppRoot(String buildNumber) { "" } - def String linuxAppRoot() { "idea-IC-$buildNumber" } + def String linuxAppRoot(String buildNumber) { "idea-IC-$buildNumber" } - def String archiveName() { "ideaIC-$buildNumber" } + def String archiveName(String buildNumber) { "ideaIC-$buildNumber" } } } \ No newline at end of file diff --git a/build/scripts/layouts.gant b/build/scripts/layouts.gant index 58289581ef97..2a35a70b6b83 100644 --- a/build/scripts/layouts.gant +++ b/build/scripts/layouts.gant @@ -21,9 +21,9 @@ target('default': "Developers update") { //when IDEA CE is updated from IDEA UE sources project should be loaded from IDEA UE directory String projectHome = isDefined("devIdeaHome") ? devIdeaHome : home loadProjectFromPath(projectHome) - def patchedDescriptorDir = patchAppDescriptor(deploy) - layoutFull(home, deploy, patchedDescriptorDir) - ant.delete(dir: patchedDescriptorDir) + def patchedAppInfo = patchApplicationInfo(deploy) + layoutFull(home, deploy, patchedAppInfo) + ant.delete(dir: patchedAppInfo) } String appInfoFileName() { @@ -64,7 +64,7 @@ List getExcludedPlugins() { return isDefined("productProperties") ? productProperties.excludedPlugins : [] } -String patchAppDescriptor(String targetDirectory) { +File patchApplicationInfo(String targetDirectory) { def patchedDirectory = "${targetDirectory}/../patched" ant.delete(dir: patchedDirectory) @@ -79,10 +79,10 @@ String patchAppDescriptor(String targetDirectory) { ant.replace(file: "$patchedDirectory/${appInfoFileName()}", token: "__BUILD_NUMBER__", value: "${code}-$snapshot") ant.replace(file: "$patchedDirectory/${appInfoFileName()}", token: "__BUILD_DATE__", value: new Date().format("yyyyMMddHHmm")) - return patchedDirectory + return new File(patchedDirectory, "idea/${appInfoFileName()}") } -def layoutFull(String home, String targetDirectory, String patchedDescriptorDir = null) { +def layoutFull(String home, String targetDirectory, File patchedApplicationInfo = null) { projectBuilder.stage("layout to $targetDirectory") List jpsCommonModules = ["jps-model-impl", "jps-model-serialization"] @@ -197,24 +197,24 @@ def layoutFull(String home, String targetDirectory, String patchedDescriptorDir module("platform-resources") def appInfoModule = appInfoModule() - def appInfoInCommunity = "community-resources".equals(appInfoModule) - - if (appInfoInCommunity) { + if ("community-resources" == appInfoModule) { module("community-resources") { - if (patchedDescriptorDir != null) { - exclude(name: appInfoFileName()) + if (patchedApplicationInfo != null) { + exclude(name: "idea/$patchedApplicationInfo.name") } } } else { module("community-resources") module(appInfoModule) { - if (patchedDescriptorDir != null) { - exclude(name: appInfoFileName()) + if (patchedApplicationInfo != null) { + exclude(name: "idea/$patchedApplicationInfo.name") } } } - if (patchedDescriptorDir != null) { - fileset(dir: patchedDescriptorDir) + if (patchedApplicationInfo != null) { + dir("idea") { + fileset(file: patchedApplicationInfo.path) + } } }