build scripts redesign: support building exe installers

This commit is contained in:
nik
2016-07-12 10:53:01 +03:00
parent 81d3142d62
commit e069644ca0
12 changed files with 209 additions and 216 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

-5
View File
@@ -1,5 +0,0 @@
; Installer images
!define IMAGES_LOCATION "${BASE_DIR}\build\images\idea"
!define PRODUCT_PROPERTIES_FILE "${BASE_DIR}\out\release\dist.all.ce\bin\idea.properties"
!define PRODUCT_VM_OPTIONS_NAME idea*.exe.vmoptions
!define PRODUCT_VM_OPTIONS_FILE "${BASE_DIR}\out\release\dist.win.ce\bin\${PRODUCT_VM_OPTIONS_NAME}"
-16
View File
@@ -1,16 +0,0 @@
!define MANUFACTURER "JetBrains"
!define MUI_PRODUCT "IntelliJ IDEA Community Edition"
!define PRODUCT_FULL_NAME "IntelliJ IDEA Community Edition"
!define PRODUCT_EXE_FILE "idea.exe"
!define PRODUCT_EXE_FILE_64 "idea64.exe"
!define PRODUCT_ICON_FILE "idea_CEinst.ico"
!define PRODUCT_UNINST_ICON_FILE "idea_CEuninst.ico"
!define PRODUCT_LOGO_FILE "logoCE.bmp"
!define PRODUCT_HEADER_FILE "headerlogoCE.bmp"
!define ASSOCIATION ".java,.groovy,.kt"
!define UNINSTALL_WEB_PAGE "https://www.jetbrains.com/idea/uninstall/?edition=IC-__VERSION_MAJOR__.__VERSION_MINOR__"
; if SHOULD_SET_DEFAULT_INSTDIR != 0 then default installation directory will be directory where highest-numbered IDEA build has been installed
; set to 1 for release build
!define SHOULD_SET_DEFAULT_INSTDIR "0"
@@ -38,6 +38,7 @@ class BuildOptions {
static final MAC_DMG_STEP = "mac_dmg"
static final LINUX_DISTRIBUTION_STEP = "linux_dist"
static final WINDOWS_DISTRIBUTION_STEP = "windows_dist"
static final WINDOWS_EXE_INSTALLER_STEP = "windows_exe_installer"
static final CROSS_PLATFORM_DISTRIBUTION_STEP = "cross_platform_dist"
/**
@@ -31,6 +31,8 @@ public abstract class ProductProperties {
*/
boolean toolsJarRequired = false
String fullNameIncludingEdition = null
abstract def String systemSelector(ApplicationInfoProperties applicationInfo)
String additionalIDEPropertiesFilePath
@@ -46,6 +48,10 @@ public abstract class ProductProperties {
abstract def String archiveName(String buildNumber)
String uninstallFeedbackPageUrl(ApplicationInfoProperties applicationInfo) {
return null
}
boolean setPluginAndIDEVersionInPluginXml = true
String ideJvmArgs = ""
@@ -77,6 +83,15 @@ public abstract class ProductProperties {
class WindowsProductProperties {
boolean includeBatchLauncher = true
boolean bundleJre = true
boolean associateIpr = true
/**
* Path to a directory containing images for installer: logo.bpm, headerlogo.bpm, install.icon, uninstall.ico
*/
String installerImagesPath
/**
* List of file extensions (starting with dot) which need to be associated with the product
*/
List<String> fileAssociations = []
}
class MacProductProperties {
@@ -1,195 +0,0 @@
/*
* 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.openapi.util.SystemInfoRt
import org.codehaus.gant.GantBuilder
import org.jetbrains.jps.gant.JpsGantProjectBuilder
/**
* @author nik
*/
class WinInstallerBuilder {
GantBuilder ant
JpsGantProjectBuilder projectBuilder
ApplicationInfoProperties applicationInfo
/**
* Path to a directory where project sources are located. It will be used to replace 'BASE_DIR' in *.nsi files.
*/
String baseDirectory
/**
* Path to a directory where IntelliJ IDEA community sources are located
*/
String communityHome
/**
* Path to a directory where artifacts are stored
*/
String artifactsPath
/**
* Path to a directory where temporary files can be stored
*/
String sandboxPath
/**
* Prefix for the output file name. The value of buildNumber will be appended to this prefix.
*/
String outNamePrefix
/**
* Short build number without product code (e.g. 142.239)
*/
String buildNumber
/**
* Name which will be used for default config/system paths, should include the product name and version. E.g. for IntelliJ Ultimate 16 it is
* 'IntelliJIdea16', so the settings and system files will be stored in $USER_HOME/.IntelliJIdea16 by default.
*/
String systemSelector
/**
* Determines whether tools.jar should be included to the bundled JDK
*/
boolean includeToolsJar = true
boolean associateIpr = true
/**
* Path to a JDK 8 zip file which should be bundled with the application
*/
String winJDKZipPath
/**
* Builds .exe installer. If build/lib/jet-sign.jar exists in baseDirectory it will be used to sign the created .exe file.
*
* @param pathsToInclude list of paths to directories which contents should be included to the product distribution
* @param stringsFile path to *.nsi file where installers variables are defined (use build/conf/nsis/stringsCE.nsi as a reference)
* @param pathsFile path to *.nsi file where installers path variables are defined (use build/conf/nsis/pathsCE.nsi as a reference)
* @return path to the created installer file
*/
def buildInstaller(List<String> pathsToInclude, String stringsFile, String pathsFile) {
if (!SystemInfoRt.isWindows && !SystemInfoRt.isLinux) {
projectBuilder.warning("Windows installer can be built only under Windows or Linux")
return null
}
projectBuilder.stage("Building Windows installer")
String outFileName = "${outNamePrefix}${buildNumber}"
ant.taskdef(name: "nsis", classname: "com.intellij.internalUtilities.ant.NsiFiles", classpath: "$communityHome/build/lib/NsiFiles.jar")
def box = sandboxPath
ant.mkdir(dir: "$box/bin")
ant.mkdir(dir: "$box/nsiconf")
if (winJDKZipPath != null) {
ant.mkdir(dir: "$box/jre")
ant.unzip(dest: "$box/jre", src: winJDKZipPath)
ant.copy(todir: "$box/bin") {
fileset(dir: "$box/jre/jre/bin") {
include(name: "msvcr71.dll")
}
}
}
ant.copy(todir: "$box/nsiconf") {
fileset(dir: "$communityHome/build/conf/nsis") {
include(name: "*")
exclude(name: "version*")
exclude(name: "strings*")
exclude(name: "paths*")
}
}
if (applicationInfo.isEAP) {
ant.copy(file: "$communityHome/build/conf/nsis/version.eap.nsi",
tofile: "$box/nsiconf/version.nsi", overwrite: true)
}
else {
ant.copy(file: "$communityHome/build/conf/nsis/version.nsi",
tofile: "$box/nsiconf/version.nsi", overwrite: true)
}
ant.copy(file: pathsFile, toFile: "$box/nsiconf/paths.nsi", overwrite: true)
ant.nsis(instfile: "$box/nsiconf/idea_win.nsh", uninstfile: "$box/nsiconf/unidea_win.nsh") {
pathsToInclude.each {
ant.fileset(dir: it, includes: "**/*") {
exclude(name: "**/idea.properties")
exclude(name: "**/*.vmoptions")
}
}
ant.fileset(dir: box, includes: "bin/msvcr71.dll")
if (winJDKZipPath != null) {
ant.fileset(dir: box, includes: "jre/**/*")
if (includeToolsJar) {
ant.fileset(dir: box) {
include(name: "jre/lib/tools.jar")
}
}
}
}
ant.copy(file: stringsFile, toFile: "$box/nsiconf/strings.nsi", overwrite: true)
ant.replace(file: "$box/nsiconf/strings.nsi") {
replacefilter(token: "__VERSION_MAJOR__", value: applicationInfo.majorVersion)
replacefilter(token: "__VERSION_MINOR__", value: applicationInfo.minorVersion)
}
ant.replace(file: "$box/nsiconf/version.nsi") {
replacefilter(token: "__BUILD_NUMBER__", value: buildNumber)
replacefilter(token: "__VERSION_MAJOR__", value: applicationInfo.majorVersion)
replacefilter(token: "__VERSION_MINOR__", value: applicationInfo.minorVersion)
replacefilter(token: "__PRODUCT_PATHS_SELECTOR__", value: systemSelector)
}
ant.unzip(src: "$communityHome/build/tools/NSIS.zip", dest: box)
if (SystemInfoRt.isWindows) {
ant.exec(command: "\"${box}/NSIS/makensis.exe\"" +
" /DBASE_DIR=\"$baseDirectory\"" +
" /DCOMMUNITY_DIR=\"$communityHome\"" +
" /DIPR=\"${associateIpr}\"" +
" /DOUT_FILE=\"${outFileName}\"" +
" /DOUT_DIR=\"$artifactsPath\"" +
" \"${box}/nsiconf/idea.nsi\"")
}
else if (SystemInfoRt.isLinux) {
ant.exec(command: "makensis" +
" '-X!AddPluginDir \"${box}/NSIS/Plugins\"'" +
" '-X!AddIncludeDir \"${box}/NSIS/Include\"'" +
" -DBASE_DIR=\"$baseDirectory\"" +
" -DCOMMUNITY_DIR=\"$communityHome\"" +
" -DIPR=\"${associateIpr}\"" +
" -DOUT_FILE=\"${outFileName}\"" +
" -DOUT_DIR=\"$artifactsPath\"" +
" \"${box}/nsiconf/idea.nsi\"")
}
def installerPath = "$artifactsPath/${outFileName}.exe"
if (!new File(installerPath).exists()) {
projectBuilder.error("Installer wasn't created.")
}
def signJarPath = "$baseDirectory/build/lib/jet-sign.jar"
if (new File(signJarPath).exists()) {
projectBuilder.stage("Signing $installerPath")
ant.taskdef(name: "jet-sign", classname: "jetbrains.sign.JetSignTask") {
classpath(path: signJarPath)
}
ant."jet-sign"() {
ant.fileset(dir: artifactsPath) {
include(name: "${outFileName}.exe")
}
}
projectBuilder.stage("Signing done")
}
else {
projectBuilder.warning("$signJarPath not found, installer won't be signed")
}
return installerPath
}
}
@@ -0,0 +1,180 @@
/*
* 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.SystemInfoRt
import org.codehaus.gant.GantBuilder
import org.jetbrains.intellij.build.BuildContext
/**
* @author nik
*/
class WinExeInstallerBuilder {
private final BuildContext buildContext
private final GantBuilder ant
WinExeInstallerBuilder(BuildContext buildContext) {
this.buildContext = buildContext
ant = buildContext.ant
}
void buildInstaller(String winDistPath) {
if (!SystemInfoRt.isWindows && !SystemInfoRt.isLinux) {
buildContext.messages.warning("Windows installer can be built only under Windows or Linux")
return
}
String communityHome = buildContext.paths.communityHome
String outFileName = buildContext.productProperties.archiveName(buildContext.buildNumber)
buildContext.messages.progress("Building Windows installer $outFileName")
ant.taskdef(name: "nsis", classname: "com.intellij.internalUtilities.ant.NsiFiles", classpath: "$communityHome/build/lib/NsiFiles.jar")
def box = "$buildContext.paths.temp/winInstaller"
ant.mkdir(dir: "$box/bin")
ant.mkdir(dir: "$box/nsiconf")
def bundleJre = buildContext.productProperties.windows.bundleJre
if (bundleJre && !new File(buildContext.paths.winJre).exists()) {
buildContext.messages.info("JRE won't be bundled with Windows installer because JRE directory doesn't exist: ${buildContext.paths.winJre}")
bundleJre = false
}
if (bundleJre) {
ant.copy(todir: "$box/bin") {
fileset(dir: "${buildContext.paths.winJre}/jre/bin") {
include(name: "msvcr71.dll")
}
}
}
ant.copy(todir: "$box/nsiconf") {
fileset(dir: "$communityHome/build/conf/nsis") {
include(name: "*")
exclude(name: "strings*")
exclude(name: "paths*")
exclude(name: "version*")
}
}
ant.nsis(instfile: "$box/nsiconf/idea_win.nsh", uninstfile: "$box/nsiconf/unidea_win.nsh") {
[buildContext.paths.distAll, winDistPath].each {
ant.fileset(dir: it, includes: "**/*") {
exclude(name: "**/idea.properties")
exclude(name: "**/*.vmoptions")
}
}
if (bundleJre) {
ant.fileset(dir: box, includes: "bin/msvcr71.dll")
ant.fileset(dir: buildContext.paths.winJre, includes: "jre/**/*")
//todo[nik] how tools.jar is excluded?
if (buildContext.productProperties.toolsJarRequired) {
ant.fileset(dir: buildContext.paths.winJre) {
include(name: "jre/lib/tools.jar")
}
}
}
}
prepareConfigurationFiles(box, winDistPath)
ant.unzip(src: "$communityHome/build/tools/NSIS.zip", dest: box)
if (SystemInfoRt.isWindows) {
ant.exec(command: "\"${box}/NSIS/makensis.exe\"" +
" /DCOMMUNITY_DIR=\"$communityHome\"" +
" /DIPR=\"${buildContext.productProperties.windows.associateIpr}\"" +
" /DOUT_FILE=\"${outFileName}\"" +
" /DOUT_DIR=\"${buildContext.paths.artifacts}\"" +
" \"${box}/nsiconf/idea.nsi\"")
}
else if (SystemInfoRt.isLinux) {
ant.exec(command: "makensis" +
" '-X!AddPluginDir \"${box}/NSIS/Plugins\"'" +
" '-X!AddIncludeDir \"${box}/NSIS/Include\"'" +
" -DCOMMUNITY_DIR=\"$communityHome\"" +
" -DIPR=\"${buildContext.productProperties.windows.associateIpr}\"" +
" -DOUT_FILE=\"${outFileName}\"" +
" -DOUT_DIR=\"${buildContext.paths.artifacts}\"" +
" \"${box}/nsiconf/idea.nsi\"")
}
def installerPath = "${buildContext.paths.artifacts}/${outFileName}.exe"
if (!new File(installerPath).exists()) {
buildContext.messages.error("Windows installer wasn't created.")
}
//todo[nik] improve, probably it would be better to automatically download jet-sign.jar or pass it via property
def signJarPath = "${buildContext.paths.projectHome}/build/lib/jet-sign.jar"
if (new File(signJarPath).exists()) {
buildContext.messages.progress("Signing $installerPath")
ant.taskdef(name: "jet-sign", classname: "jetbrains.sign.JetSignTask") {
classpath(path: signJarPath)
}
ant."jet-sign"() {
ant.fileset(dir: buildContext.paths.artifacts) {
include(name: "${outFileName}.exe")
}
}
buildContext.messages.info("Signing done")
}
else {
buildContext.messages.warning("$signJarPath not found, installer won't be signed")
}
buildContext.notifyArtifactBuilt(installerPath)
}
private void prepareConfigurationFiles(String box, String winDistPath) {
new File(box, "nsiconf/paths.nsi").text = """
!define IMAGES_LOCATION "${buildContext.productProperties.windows.installerImagesPath}"
!define PRODUCT_PROPERTIES_FILE "$winDistPath/bin/idea.properties"
!define PRODUCT_VM_OPTIONS_NAME ${buildContext.fileNamePrefix}*.exe.vmoptions
!define PRODUCT_VM_OPTIONS_FILE "$winDistPath/bin/\${PRODUCT_VM_OPTIONS_NAME}"
"""
String fullProductName = buildContext.productProperties.fullNameIncludingEdition ?: buildContext.applicationInfo.productName
def extensionsList = buildContext.productProperties.windows.fileAssociations
def fileAssociations = extensionsList.isEmpty() ? "NoAssociation" : extensionsList.join(",")
new File(box, "nsiconf/strings.nsi").text = """
!define MANUFACTURER "JetBrains"
!define MUI_PRODUCT "$fullProductName"
!define PRODUCT_FULL_NAME "$fullProductName"
!define PRODUCT_EXE_FILE "${buildContext.fileNamePrefix}.exe"
!define PRODUCT_EXE_FILE_64 "${buildContext.fileNamePrefix}64.exe"
!define PRODUCT_ICON_FILE "install.ico"
!define PRODUCT_UNINST_ICON_FILE "uninstall.ico"
!define PRODUCT_LOGO_FILE "logo.bmp"
!define PRODUCT_HEADER_FILE "headerlogo.bmp"
!define ASSOCIATION "$fileAssociations"
!define UNINSTALL_WEB_PAGE "${buildContext.productProperties.uninstallFeedbackPageUrl(buildContext.applicationInfo) ?: "feedback_web_page"}"
; if SHOULD_SET_DEFAULT_INSTDIR != 0 then default installation directory will be directory where highest-numbered IDEA build has been installed
; set to 1 for release build
!define SHOULD_SET_DEFAULT_INSTDIR "0"
"""
def versionString = buildContext.applicationInfo.isEAP ? "\${VER_BUILD}" : "\${MUI_VERSION_MAJOR}.\${MUI_VERSION_MINOR}"
new File(box, "nsiconf/version.nsi").text = """
!define MUI_VERSION_MAJOR "${buildContext.applicationInfo.majorVersion}"
!define MUI_VERSION_MINOR "${buildContext.applicationInfo.minorVersion}"
!define VER_BUILD ${buildContext.buildNumber}
!define PRODUCT_WITH_VER "\${MUI_PRODUCT} $versionString"
!define PRODUCT_FULL_NAME_WITH_VER "\${PRODUCT_FULL_NAME} $versionString"
!define PRODUCT_PATHS_SELECTOR "${buildContext.systemSelector}"
!define PRODUCT_SETTINGS_DIR ".\${PRODUCT_PATHS_SELECTOR}"
"""
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.intellij.build.impl
import org.jetbrains.intellij.build.BuildContext
import org.jetbrains.intellij.build.BuildOptions
import org.jetbrains.jps.model.module.JpsModuleSourceRoot
/**
* @author nik
@@ -46,6 +47,10 @@ class WindowsDistributionBuilder {
buildWinLauncher(JvmArchitecture.x64)
buildContext.productProperties.customWinLayout(winDistPath)
buildWinZip()
buildContext.executeStep("Build Windows Exe Installer", BuildOptions.WINDOWS_EXE_INSTALLER_STEP) {
new WinExeInstallerBuilder(buildContext).buildInstaller(winDistPath)
}
}
//todo[nik] rename
+8
View File
@@ -24,6 +24,7 @@ def getProperties(String home) {
platformPrefix = "Idea"
code = "IC"
appInfoModule = "community-resources"
fullNameIncludingEdition = "IntelliJ IDEA Community Edition"
additionalIDEPropertiesFilePath = "$home/build/conf/ideaCE.properties"
exe_launcher_properties = "$home/build/conf/ideaCE-launcher.properties"
exe64_launcher_properties = "$home/build/conf/ideaCE64-launcher.properties"
@@ -36,6 +37,8 @@ def getProperties(String home) {
ico = "$home/platform/icons/src/idea_CE.ico"
windows.bundleJre = true
windows.installerImagesPath = "$home/build/conf/ideaCE/win/images"
windows.fileAssociations = [".java", ".groovy", ".kt"]
mac.helpId = "IJ"
mac.urlSchemes = ["idea"]
@@ -44,6 +47,11 @@ def getProperties(String home) {
mac.dmgImagePath = "$home/build/conf/mac/communitydmg.png"
}
@Override
String uninstallFeedbackPageUrl(ApplicationInfoProperties applicationInfo) {
return "https://www.jetbrains.com/idea/uninstall/?edition=IC-${applicationInfo.majorVersion}.${applicationInfo.minorVersion}"
}
def String appInfoFile() {
"${projectBuilder.moduleOutput(findModule("community-resources"))}/idea/IdeaApplicationInfo.xml"
}