/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.tools; import com.liferay.portal.kernel.util.CharPool; import com.liferay.portal.kernel.util.FileUtil; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.NaturalOrderStringComparator; import com.liferay.portal.kernel.util.OSDetector; import com.liferay.portal.kernel.util.PropertiesUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.util.FileImpl; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import org.apache.tools.ant.DirectoryScanner; /** * @author Brian Wing Shun Chan */ public class PluginsSummaryBuilder { public static void main(String[] args) throws Exception { ToolDependencies.wireBasic(); File pluginsDir = new File(System.getProperty("plugins.dir")); new PluginsSummaryBuilder(pluginsDir); } public PluginsSummaryBuilder(File pluginsDir) throws Exception { _pluginsDir = pluginsDir; String latestHASH = null; latestHASH = _getLatestHASH(pluginsDir); _latestHASH = latestHASH; _createPluginsSummary(); } private void _createPluginsSummary() throws Exception { DirectoryScanner directoryScanner = new DirectoryScanner(); directoryScanner.setBasedir(_pluginsDir); directoryScanner.setExcludes( new String[] {"**\\tmp\\**", "**\\tools\\**"}); directoryScanner.setIncludes( new String[] {"**\\liferay-plugin-package.properties"}); directoryScanner.scan(); String[] fileNames = directoryScanner.getIncludedFiles(); Arrays.sort(fileNames); _createPluginsSummary(fileNames); } private void _createPluginsSummary(String[] fileNames) throws Exception { StringBundler sb = new StringBundler(); sb.append("<plugins-summary>\n"); for (String fileName : fileNames) { fileName = StringUtil.replace( fileName, CharPool.BACK_SLASH, CharPool.SLASH); _createPluginSummary(sb, fileName); } for (String author : _distinctAuthors) { sb.append("\t<author>"); sb.append(author); sb.append("</author>\n"); } for (String license : _distinctLicenses) { sb.append("\t<license>"); sb.append(license); sb.append("</license>\n"); } sb.append("</plugins-summary>"); FileUtil.write(_pluginsDir + "/summary.xml", sb.toString()); } private void _createPluginSummary(StringBundler sb, String fileName) throws Exception { String content = FileUtil.read(fileName); int x = fileName.indexOf(StringPool.SLASH); String type = fileName.substring(0, x); if (type.endsWith("s")) { type = type.substring(0, type.length() - 1); } x = fileName.indexOf(StringPool.SLASH, x) + 1; int y = fileName.indexOf(StringPool.SLASH, x); String artifactId = fileName.substring(x, y); Properties properties = PropertiesUtil.load(content); String name = _readProperty(properties, "name"); String tags = _readProperty(properties, "tags"); String shortDescription = _readProperty( properties, "short-description"); String longDescription = _readProperty(properties, "long-description"); String changeLog = _readProperty(properties, "change-log"); String pageURL = _readProperty(properties, "page-url"); String author = _readProperty(properties, "author"); String licenses = _readProperty(properties, "licenses"); String liferayVersions = _readProperty(properties, "liferay-versions"); _distinctAuthors.add(author); _distinctLicenses.add(licenses); sb.append("\t<plugin>\n"); _writeElement(sb, "artifact-id", artifactId, 2); _writeElement(sb, "name", name, 2); _writeElement(sb, "type", type, 2); _writeElement(sb, "tags", tags, 2); _writeElement(sb, "short-description", shortDescription, 2); _writeElement(sb, "long-description", longDescription, 2); _writeElement(sb, "change-log", changeLog, 2); _writeElement(sb, "page-url", pageURL, 2); _writeElement(sb, "author", author, 2); _writeElement(sb, "licenses", licenses, 2); _writeElement(sb, "liferay-versions", liferayVersions, 2); sb.append("\t\t<releng>\n"); sb.append(_readReleng(fileName, properties)); sb.append("\t\t</releng>\n"); sb.append("\t</plugin>\n"); } private Set<String> _extractTicketIds(File pluginDir, String range) throws Exception { Set<String> ticketIds = new TreeSet<>( new NaturalOrderStringComparator()); Runtime runtime = Runtime.getRuntime(); String command = "git log " + range + " ."; if (OSDetector.isWindows()) { command = "cmd /c " + command; } Process process = runtime.exec(command, null, pluginDir); String content = StringUtil.read(process.getInputStream()); content = StringUtil.replace(content, '\n', ' '); for (String ticketIdPrefix : _TICKET_ID_PREFIXES) { int x = 0; while (true) { x = content.indexOf(ticketIdPrefix + "-", x); if (x == -1) { break; } int y = x + ticketIdPrefix.length() + 1; while (true) { if ((y + 1) > content.length()) { break; } if (Character.isDigit(content.charAt(y))) { y++; } else { break; } } String ticketId = content.substring(x, y); ticketIds.add(ticketId); x = y; } } File buildXmlFile = new File(pluginDir, "build.xml"); System.out.println("## read a " + buildXmlFile); String buildXmlContent = _fileUtil.read(buildXmlFile); int x = buildXmlContent.indexOf("import.shared"); if (x == -1) { return ticketIds; } x = buildXmlContent.indexOf("value=\"", x); x = buildXmlContent.indexOf("\"", x); int y = buildXmlContent.indexOf("\" />", x); if ((x == -1) || (y == -1)) { return ticketIds; } String[] importShared = StringUtil.split( buildXmlContent.substring(x + 1, y)); if (importShared.length == 0) { return ticketIds; } for (String currentImportShared : importShared) { File currentImportSharedDir = new File( pluginDir, "../../shared/" + currentImportShared); if (!currentImportSharedDir.exists()) { continue; } ticketIds.addAll(_extractTicketIds(currentImportSharedDir, range)); } return ticketIds; } private String _getChangeLogEntry( int changeLogVersion, String range, String ticketIdsString) { StringBundler sb = new StringBundler(8); if (changeLogVersion > 1) { sb.append("\n\n"); } sb.append("#\n"); sb.append("# Module Incremental Version "); sb.append(changeLogVersion); sb.append("\n#\n"); sb.append(range); sb.append("="); sb.append(ticketIdsString); return sb.toString(); } private String _getLatestHASH(File pluginDir) throws Exception { Runtime runtime = Runtime.getRuntime(); String command = "git rev-parse HEAD"; if (OSDetector.isWindows()) { command = "cmd /c " + command; } Process process = runtime.exec(command, null, pluginDir); return StringUtil.read(process.getInputStream()); } private String _readProperty(Properties properties, String key) { return GetterUtil.getString(properties.getProperty(key)); } private String _readReleng( String fileName, Properties pluginPackageProperties) throws Exception { int x = fileName.indexOf("WEB-INF"); String relativeWebInfDirName = fileName.substring(0, x + 8); String fullWebInfDirName = _pluginsDir + StringPool.SLASH + relativeWebInfDirName; String relengPropertiesFileName = fullWebInfDirName + "liferay-releng.properties"; Properties relengProperties = null; if (FileUtil.exists(relengPropertiesFileName)) { String relengPropertiesContent = FileUtil.read( relengPropertiesFileName); relengProperties = PropertiesUtil.load(relengPropertiesContent); } else { relengProperties = new Properties(); } String relengPropertiesContent = _updateRelengPropertiesFile( relengPropertiesFileName, relengProperties); relengProperties = PropertiesUtil.load(relengPropertiesContent); StringBundler sb = new StringBundler(); _writeElement(sb, "bundle", relengProperties, 3); _writeElement(sb, "category", relengProperties, 3); _writeElement(sb, "demo-url", relengProperties, 3); _writeElement(sb, "dependent-apps", relengProperties, 3); if (FileUtil.exists(fullWebInfDirName + "releng/icons/90x90.png")) { _writeElement( sb, "icon", relativeWebInfDirName + "releng/icons/90x90.png", 3); } _writeElement(sb, "labs", relengProperties, 3); _writeElement(sb, "marketplace", relengProperties, 3); _writeElement(sb, "public", relengProperties, 3); String fullScreenshotsDirName = fullWebInfDirName + "releng/screenshots/"; String relativeScreenshotsDirName = relativeWebInfDirName + "releng/screenshots/"; if (FileUtil.exists(fullScreenshotsDirName)) { String[] screenshotsFileNames = FileUtil.listFiles( fullScreenshotsDirName); Arrays.sort(screenshotsFileNames); for (String screenshotsFileName : screenshotsFileNames) { if (screenshotsFileName.equals("Thumbs.db") || screenshotsFileName.endsWith(".png")) { FileUtil.delete( fullScreenshotsDirName + screenshotsFileName); } if (!screenshotsFileName.endsWith(".jpg")) { continue; } _writeElement( sb, "screenshot", relativeScreenshotsDirName + screenshotsFileName, 3); } } _writeElement(sb, "support-url", relengProperties, 3); _writeElement(sb, "supported", relengProperties, 3); File relengChangeLogFile = new File( fullWebInfDirName + "liferay-releng.changelog"); if (GetterUtil.getBoolean( relengProperties.getProperty("marketplace"))) { _updateRelengChangeLogFile( pluginPackageProperties, relengChangeLogFile, relengProperties); } else { relengChangeLogFile.delete(); } return sb.toString(); } private void _updateRelengChangeLogFile( Properties pluginPackageProperties, File relengChangeLogFile, Properties relengProperties) throws Exception { StringBundler sb = new StringBundler(); int changeLogVersion = 0; int moduleIncrementalVersion = GetterUtil.getInteger( pluginPackageProperties.getProperty("module-incremental-version")); if (!relengChangeLogFile.exists()) { FileUtil.write(relengChangeLogFile, "TEMP="); } String relengChangeLogContent = FileUtil.read(relengChangeLogFile); List<String> relengChangeLogEntries = new ArrayList<>(); String[] relengChangeLogEntriesArray = StringUtil.split( relengChangeLogContent, "\n"); for (int i = 0; i < relengChangeLogEntriesArray.length; i++) { String relengChangeLogEntry = relengChangeLogEntriesArray[i]; if (Validator.isNull(relengChangeLogEntry) || relengChangeLogEntry.startsWith("#")) { continue; } relengChangeLogEntries.add(relengChangeLogEntry); if (((i + 1) == relengChangeLogEntriesArray.length) && !relengChangeLogEntry.contains("HEAD=") && !relengChangeLogEntry.contains("TEMP=") && !relengChangeLogEntry.contains(_latestHASH) && !relengChangeLogEntries.isEmpty()) { int x = relengChangeLogEntry.indexOf(".."); int y = relengChangeLogEntry.indexOf("=", x); String range = relengChangeLogEntry.substring(x + 2, y) + "^.." + _latestHASH; relengChangeLogEntries.add(range); continue; } } File webInfDir = relengChangeLogFile.getParentFile(); File docrootDir = webInfDir.getParentFile(); File pluginDir = docrootDir.getParentFile(); for (int i = 0; i < relengChangeLogEntries.size(); i++) { String relengChangeLogEntry = relengChangeLogEntries.get(i); String[] relengChangeLogEntryParts = StringUtil.split( relengChangeLogEntry, "="); String range = relengChangeLogEntryParts[0]; if (range.equals("TEMP")) { changeLogVersion++; sb.append( _getChangeLogEntry( changeLogVersion, range, StringPool.BLANK)); break; } Set<String> ticketIds = _extractTicketIds(pluginDir, range); if (range.endsWith("^.." + _latestHASH) && ticketIds.isEmpty() && (relengChangeLogEntries.size() > 1)) { continue; } if (ticketIds.isEmpty()) { System.out.println( pluginDir + " does not have changes for range " + range); } String[] dependentApps = StringUtil.split( relengProperties.getProperty("dependent-apps")); for (String dependentApp : dependentApps) { dependentApp = dependentApp.trim(); if (dependentApp.equals("resources-impoter-web")) { continue; } String dependentAppDirName = null; if (dependentApp.endsWith("-hook")) { dependentAppDirName = "hooks"; } else if (dependentApp.endsWith("-layouttpl")) { dependentAppDirName = "layouttpl"; } else if (dependentApp.endsWith("-portlet")) { dependentAppDirName = "portlets"; } else if (dependentApp.endsWith("-theme")) { dependentAppDirName = "themes"; } else if (dependentApp.endsWith("-web")) { dependentAppDirName = "webs"; } File dependentAppDir = new File( _pluginsDir, dependentAppDirName + "/" + dependentApp); if (!dependentAppDir.exists()) { throw new RuntimeException( dependentAppDir + " does not exist"); } ticketIds.addAll(_extractTicketIds(dependentAppDir, range)); } String ticketIdsString = StringUtil.merge( ticketIds.toArray(new String[ticketIds.size()]), " "); changeLogVersion++; sb.append( _getChangeLogEntry(changeLogVersion, range, ticketIdsString)); } File pluginPackagePropertiesFile = new File( webInfDir, "liferay-plugin-package.properties"); String pluginPackagePropertiesContent = FileUtil.read( pluginPackagePropertiesFile); if (!pluginPackagePropertiesContent.contains("long-description")) { int x = pluginPackagePropertiesContent.indexOf("change-log="); pluginPackagePropertiesContent = pluginPackagePropertiesContent.substring(0, x) + "long-description=\n" + pluginPackagePropertiesContent.substring(x); } if (moduleIncrementalVersion != changeLogVersion) { pluginPackagePropertiesContent = StringUtil.replace( pluginPackagePropertiesContent, "module-incremental-version=" + moduleIncrementalVersion, "module-incremental-version=" + changeLogVersion); } FileUtil.write( pluginPackagePropertiesFile, pluginPackagePropertiesContent); FileUtil.write(relengChangeLogFile, sb.toString()); File relengChangeLogMD5File = new File( webInfDir, "liferay-releng.changelog.md5"); String md5Checksum = FileUtil.getMD5Checksum(relengChangeLogFile); FileUtil.write(relengChangeLogMD5File, md5Checksum); } private String _updateRelengPropertiesFile( String relengPropertiesFileName, Properties relengProperties) throws Exception { StringBundler sb = new StringBundler(); _writeProperty(sb, relengProperties, "bundle", "false"); _writeProperty(sb, relengProperties, "category", ""); _writeProperty(sb, relengProperties, "demo-url", ""); _writeProperty(sb, relengProperties, "dependent-apps", ""); _writeProperty(sb, relengProperties, "labs", "true"); _writeProperty(sb, relengProperties, "marketplace", "false"); _writeProperty(sb, relengProperties, "public", "true"); _writeProperty(sb, relengProperties, "support-url", ""); _writeProperty(sb, relengProperties, "supported", "false"); String relengPropertiesContent = sb.toString(); FileUtil.write(relengPropertiesFileName, relengPropertiesContent); return relengPropertiesContent; } private void _writeElement( StringBundler sb, String name, Properties properties, int tabsCount) { _writeElement(sb, name, _readProperty(properties, name), tabsCount); } private void _writeElement( StringBundler sb, String name, String value, int tabsCount) { for (int i = 0; i < tabsCount; i++) { sb.append("\t"); } sb.append("<"); sb.append(name); sb.append("><![CDATA["); sb.append(value); sb.append("]]></"); sb.append(name); sb.append(">\n"); } private void _writeProperty( StringBundler sb, Properties properties, String key, String defaultValue) { String value = GetterUtil.getString( properties.getProperty(key), defaultValue); if (sb.index() > 0) { sb.append(StringPool.NEW_LINE); } sb.append(key); sb.append(StringPool.EQUAL); sb.append(value); } private static final String[] _TICKET_ID_PREFIXES = {"CLDSVCS", "LPS", "SOS", "SYNC"}; private static final FileImpl _fileUtil = FileImpl.getInstance(); private final Set<String> _distinctAuthors = new TreeSet<>(); private final Set<String> _distinctLicenses = new TreeSet<>(); private final String _latestHASH; private final File _pluginsDir; }