diff --git a/build/groovy/org/jetbrains/intellij/build/MacDistributionBuilder.groovy b/build/groovy/org/jetbrains/intellij/build/MacDistributionBuilder.groovy new file mode 100644 index 000000000000..f9dd0afd5256 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/MacDistributionBuilder.groovy @@ -0,0 +1,203 @@ +/* + * 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.PathUtilRt +import org.apache.tools.ant.types.Path +import org.apache.tools.ant.util.SplitClassLoader +import org.codehaus.gant.GantBuilder +import org.jetbrains.jps.gant.JpsGantProjectBuilder + +/** + * @deprecated use {@link BuildTasks} instead. + */ +class MacDistributionBuilder { + GantBuilder ant + JpsGantProjectBuilder projectBuilder + MacHostProperties macHostProperties + /** + * Path to a directory where IntelliJ IDEA community sources are located + */ + String communityHome + /** + * Unique number for the current build (e.g. IC-142.239 for IDEA Community), it is used to create unique file name for files transferred to Mac host + */ + String fullBuildNumber + /** + * Path to a directory where artifacts are stored + */ + String artifactsPath + /** + * Path to the JDK 8 tar file to be bundled with the application + */ + String customJDKTarPath + /** + * Path to an image which will be injected into .dmg file + */ + String dmgImagePath + + + private String remoteDir + /** + * Converts ${targetFileName}.mac.zip file to ${targetFileName}.dmg installer with signed application inside + * @return path to created .dmg file + */ + String signAndBuildDmg(String targetFileName) { + defineTasks() + remoteDir = "intellij-builds/$fullBuildNumber" + def sitFilePath = "$artifactsPath/${targetFileName}.sit" + ant.copy(file: "$artifactsPath/${targetFileName}.mac.zip", tofile: sitFilePath) + ftpAction("mkdir") { + } + signMacZip(targetFileName, sitFilePath) + return buildDmg(targetFileName) + } + + private String buildDmg(String sitFileName) { + projectBuilder.stage("building .dmg") + def dmgImageCopy = "$artifactsPath/${fullBuildNumber}.png" + ant.copy(file: dmgImagePath, tofile: dmgImageCopy) + ftpAction("put") { + ant.fileset(file: dmgImageCopy) + } + ant.delete(file: dmgImageCopy) + + ftpAction("put", false, "777") { + ant.fileset(dir: "$communityHome/build/mac") { + include(name: "makedmg.sh") + include(name: "makedmg.pl") + } + } + + sshExec("$remoteDir/makedmg.sh ${sitFileName} ${fullBuildNumber}") + ftpAction("get", true, null, 3) { + ant.fileset(dir: artifactsPath) { + include(name: "${sitFileName}.dmg") + } + } + ftpAction("delete") { + ant.fileset() { + include(name: "**") + } + } + ftpAction("rmdir", true, null, 0, PathUtilRt.getParentPath(remoteDir)) { + ant.fileset() { + include(name: "${PathUtilRt.getFileName(remoteDir)}/**") + } + } + def dmgFilePath = "$artifactsPath/${sitFileName}.dmg" + if (!new File(dmgFilePath).exists()) { + projectBuilder.error("Failed to build .dmg file.") + } + return dmgFilePath + } + + private def signMacZip(String sitFileName, String sitFilePath) { + projectBuilder.stage("signing .mac.zip") + + if (new File(customJDKTarPath).exists()) { + ftpAction("put") { + ant.fileset(file: customJDKTarPath) + } + } + else { + projectBuilder.info("Custom JDK won't be bundled: $customJDKTarPath doesn't exist") + } + + projectBuilder.info("Sending $sitFilePath") + ftpAction("put") { + ant.fileset(file: sitFilePath) + } + ant.delete(file: sitFilePath) + ftpAction("put", false, "777") { + ant.fileset(dir: "$communityHome/build/mac") { + include(name: "signapp.sh") + } + } + + sshExec( + "$remoteDir/signapp.sh ${sitFileName} ${fullBuildNumber} ${macHostProperties.userName} ${macHostProperties.password} \"${macHostProperties.codesignString}\" \"${PathUtilRt.getFileName(customJDKTarPath)}\"") + ftpAction("get", true, null, 3) { + ant.fileset(dir: artifactsPath) { + include(name: "${sitFileName}.sit") + } + } + if (!new File(sitFilePath).exists()) { + projectBuilder.error("Failed to build .sit file") + } + } + + static boolean tasksDefined + + private def defineTasks() { + if (tasksDefined) return + tasksDefined = true + + /* + We need this to ensure that FTP task class isn't loaded by the main Ant classloader, otherwise Ant will try to load FTPClient class + by the main Ant classloader as well and fail because 'commons-net-*.jar' isn't included to Ant classpath. + Probably we could call FTPClient directly to avoid this hack. + */ + def ftpTaskLoaderRef = "FTP_TASK_CLASS_LOADER"; + Path ftpPath = new Path(ant.project) + ftpPath.createPathElement().setLocation(new File("$communityHome/lib/commons-net-3.3.jar")) + ftpPath.createPathElement().setLocation(new File("$communityHome/lib/ant/lib/ant-commons-net.jar")) + ant.project.addReference(ftpTaskLoaderRef, new SplitClassLoader(ant.project.getClass().getClassLoader(), ftpPath, ant.project, + ["FTP", "FTPTaskConfig"] as String[])) + ant.taskdef(name: "ftp", classname: "org.apache.tools.ant.taskdefs.optional.net.FTP", loaderRef: ftpTaskLoaderRef) + + def sshTaskLoaderRef = "SSH_TASK_CLASS_LOADER"; + Path pathSsh = new Path(ant.project) + pathSsh.createPathElement().setLocation(new File("$communityHome/lib/jsch-0.1.53.jar")) + pathSsh.createPathElement().setLocation(new File("$communityHome/lib/ant/lib/ant-jsch.jar")) + ant.project.addReference(sshTaskLoaderRef, new SplitClassLoader(ant.project.getClass().getClassLoader(), pathSsh, ant.project, + ["SSHExec", "SSHBase", "LogListener", "SSHUserInfo"] as String[])) + ant.taskdef(name: "sshexec", classname: "org.apache.tools.ant.taskdefs.optional.ssh.SSHExec", loaderRef: sshTaskLoaderRef) + } + + private void sshExec(String command) { + ant.sshexec( + host: macHostProperties.host, + username: macHostProperties.userName, + password: macHostProperties.password, + trust: "yes", + command: command + ) + } + + def ftpAction( + String action, + boolean binary = true, + String chmod = null, + int retriesAllowed = 0, + String overrideRemoteDir = null, + Closure filesets) { + Map args = [ + server : macHostProperties.host, + userid : macHostProperties.userName, + password : macHostProperties.password, + action : action, + remotedir : overrideRemoteDir ?: remoteDir, + binary : binary ? "yes" : "no", + passive : "yes", + retriesallowed: "$retriesAllowed" + ] + if (chmod != null) { + args["chmod"] = chmod + } + ant.ftp(args, filesets) + } +} diff --git a/build/groovy/org/jetbrains/intellij/build/WinInstallerBuilder.groovy b/build/groovy/org/jetbrains/intellij/build/WinInstallerBuilder.groovy new file mode 100644 index 000000000000..cc85a1579ed9 --- /dev/null +++ b/build/groovy/org/jetbrains/intellij/build/WinInstallerBuilder.groovy @@ -0,0 +1,211 @@ +/* + * 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. + */ +/* + * 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 + +/** + * @deprecated use {@link BuildTasks} instead. + */ +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 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 + } +} \ No newline at end of file