| package org.jetbrains.jps.android; |
| |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.io.FileUtilRt; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.containers.HashMap; |
| import com.intellij.util.containers.HashSet; |
| import org.jetbrains.android.util.AndroidBuildTestingManager; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.io.*; |
| import java.nio.charset.Charset; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author Eugene.Kudelevsky |
| */ |
| abstract class AndroidBuildTestingCommandExecutor implements AndroidBuildTestingManager.MyCommandExecutor { |
| public static final String ENTRY_HEADER = "______ENTRY_"; |
| |
| private volatile StringWriter myStringWriter = new StringWriter(); |
| private final List<Pair<String, Pattern>> myPathPatterns = new ArrayList<Pair<String, Pattern>>(); |
| |
| private final Set<String> myCheckedJars = new HashSet<String>(); |
| |
| public void addPathPrefix(@NotNull String id, @NotNull String prefix) { |
| myPathPatterns.add(Pair.create(id, Pattern.compile("(" + FileUtil.toSystemIndependentName(prefix) + ").*"))); |
| } |
| |
| public void addRegexPathPattern(@NotNull String id, @NotNull String regex) { |
| myPathPatterns.add(Pair.create(id, Pattern.compile("(" + regex + ")"))); |
| } |
| |
| public void addRegexPathPatternPrefix(@NotNull String id, @NotNull String regex) { |
| myPathPatterns.add(Pair.create(id, Pattern.compile("(" + regex + ").*"))); |
| } |
| |
| @NotNull |
| @Override |
| public Process createProcess(@NotNull String[] args, @NotNull Map<? extends String, ? extends String> environment) { |
| startNewEntry(); |
| final String[] argsToLog = processArgs(args); |
| logString(StringUtil.join(argsToLog, "\n")); |
| |
| if (environment.size() > 0) { |
| final StringBuilder envBuilder = new StringBuilder(); |
| |
| for (Map.Entry<? extends String, ? extends String> entry : environment.entrySet()) { |
| if (envBuilder.length() > 0) { |
| envBuilder.append(", "); |
| } |
| final String value = progessArg(entry.getValue()); |
| envBuilder.append(entry.getKey()).append("=").append(value); |
| } |
| logString("\nenv: " + envBuilder.toString()); |
| } |
| logString("\n\n"); |
| try { |
| return doCreateProcess(args, environment); |
| } |
| catch (RuntimeException e) { |
| throw e; |
| } |
| catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| public void log(@NotNull String s) { |
| startNewEntry(); |
| final String[] args = s.split("\\n"); |
| logString(StringUtil.join(processArgs(args), "\n")); |
| logString("\n\n"); |
| } |
| |
| @Override |
| public void checkJarContent(@NotNull String jarId, @NotNull String jarPath) { |
| doCheckJar(jarId, jarPath); |
| myCheckedJars.add(jarId); |
| } |
| |
| protected void doCheckJar(@NotNull String jarId, @NotNull String jarPath) { |
| } |
| |
| private synchronized void startNewEntry() { |
| logString(ENTRY_HEADER + "\n"); |
| } |
| |
| private synchronized void logString(String s) { |
| myStringWriter.write(s); |
| } |
| |
| private String[] processArgs(String[] args) { |
| final String[] result = new String[args.length]; |
| |
| for (int i = 0; i < result.length; i++) { |
| result[i] = progessArg(args[i]); |
| } |
| return result; |
| } |
| |
| private String progessArg(String arg) { |
| final String[] subargs = arg.split(File.pathSeparator); |
| final StringBuilder builder = new StringBuilder(); |
| |
| for (int i = 0; i < subargs.length; i++) { |
| String subarg = subargs[i]; |
| String s = FileUtil.toSystemIndependentName(subarg); |
| |
| if (s.endsWith(".exe")) { |
| s = FileUtilRt.getNameWithoutExtension(s); |
| } |
| for (Pair<String, Pattern> pair : myPathPatterns) { |
| final String id = pair.getFirst(); |
| final Pattern prefixPattern = pair.getSecond(); |
| final Matcher matcher = prefixPattern.matcher(s); |
| |
| if (matcher.matches()) { |
| s = "$" + id + "$" + s.substring(matcher.group(1).length()); |
| } |
| } |
| builder.append(s); |
| |
| if (i < subargs.length - 1) { |
| builder.append(File.pathSeparator); |
| } |
| } |
| return builder.toString(); |
| } |
| |
| @NotNull |
| protected abstract Process doCreateProcess(@NotNull String[] args, @NotNull Map<? extends String, ? extends String> environment) |
| throws Exception; |
| |
| @NotNull |
| public synchronized String getLog() { |
| return myStringWriter.toString(); |
| } |
| |
| public synchronized void clear() { |
| myStringWriter = new StringWriter(); |
| myCheckedJars.clear(); |
| } |
| |
| @NotNull |
| protected Set<String> getCheckedJars() { |
| return myCheckedJars; |
| } |
| |
| public static String normalizeExpectedLog(@NotNull String expectedLog, @NotNull String actualLog) { |
| |
| final String[] actualEntries = actualLog.split(AndroidBuildTestingCommandExecutor.ENTRY_HEADER); |
| final List<String> actualEntryList = new ArrayList<String>(); |
| |
| for (String entry : actualEntries) { |
| final String s = entry.trim(); |
| |
| if (s.length() == 0) { |
| continue; |
| } |
| actualEntryList.add(s); |
| } |
| final Map<String, MyLogEntry> id2logEntry = new HashMap<String, MyLogEntry>(); |
| final String[] entries = expectedLog.split(AndroidBuildTestingCommandExecutor.ENTRY_HEADER); |
| |
| for (String entry : entries) { |
| final String s = entry.trim(); |
| |
| if (s.length() == 0) { |
| continue; |
| } |
| final int newLineIdx = s.indexOf('\n'); |
| final int colonIdx = s.indexOf(':'); |
| assert colonIdx >= 0 && colonIdx < newLineIdx; |
| |
| final String id = s.substring(0, colonIdx); |
| final String depIdsStr = s.substring(colonIdx + 1, newLineIdx); |
| final String[] depIds = depIdsStr.length() > 0 |
| ? depIdsStr.trim().split(",") |
| : ArrayUtil.EMPTY_STRING_ARRAY; |
| final String content = s.substring(newLineIdx + 1).trim(); |
| id2logEntry.put(id, new MyLogEntry(content, depIds)); |
| } |
| final List<String> addedEntries = new ArrayList<String>(); |
| final Set<String> addedEntriesSet = new HashSet<String>(); |
| |
| while (addedEntries.size() < id2logEntry.size()) { |
| final List<String> candidates = new ArrayList<String>(); |
| |
| for (Map.Entry<String, MyLogEntry> entry : id2logEntry.entrySet()) { |
| final String id = entry.getKey(); |
| |
| if (addedEntriesSet.contains(id)) { |
| continue; |
| } |
| final MyLogEntry logEntry = entry.getValue(); |
| boolean canBeAdded = true; |
| |
| for (String depId : logEntry.myDepIds) { |
| if (!addedEntriesSet.contains(depId)) { |
| canBeAdded = false; |
| break; |
| } |
| } |
| |
| if (canBeAdded) { |
| candidates.add(id); |
| } |
| } |
| |
| if (candidates.size() == 0) { |
| throw new RuntimeException("The log graph contains cycles"); |
| } |
| boolean added = false; |
| |
| if (actualEntryList.size() > addedEntries.size()) { |
| final String actualEntryContent = actualEntryList.get(addedEntries.size()); |
| |
| for (String id : candidates) { |
| if (id2logEntry.get(id).myContent.equals(actualEntryContent)) { |
| addedEntries.add(id); |
| addedEntriesSet.add(id); |
| added = true; |
| break; |
| } |
| } |
| } |
| |
| if (!added) { |
| addedEntriesSet.addAll(candidates); |
| addedEntries.addAll(candidates); |
| } |
| } |
| final StringBuilder builder = new StringBuilder(); |
| |
| for (String id : addedEntries) { |
| final MyLogEntry entry = id2logEntry.get(id); |
| builder.append(entry.myContent).append("\n\n"); |
| } |
| return builder.toString(); |
| } |
| |
| public static String normalizeLog(@NotNull String log) { |
| final String[] entries = log.split(AndroidBuildTestingCommandExecutor.ENTRY_HEADER); |
| final StringBuilder result = new StringBuilder(); |
| |
| for (String entry : entries) { |
| final String s = entry.trim(); |
| |
| if (s.length() == 0) { |
| continue; |
| } |
| result.append(s).append("\n\n"); |
| } |
| return result.toString(); |
| } |
| |
| protected static class MyProcess extends Process { |
| |
| private final int myExitValue; |
| private final String myOutputText; |
| private final String myErrorText; |
| |
| protected MyProcess(int exitValue, @NotNull String outputText, @NotNull String errorText) { |
| myExitValue = exitValue; |
| myOutputText = outputText; |
| myErrorText = errorText; |
| } |
| |
| @Override |
| public OutputStream getOutputStream() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public InputStream getInputStream() { |
| return stringToInputStream(myOutputText); |
| } |
| |
| @Override |
| public InputStream getErrorStream() { |
| return stringToInputStream(myErrorText); |
| } |
| |
| private static ByteArrayInputStream stringToInputStream(String s) { |
| return new ByteArrayInputStream(s.getBytes(Charset.defaultCharset())); |
| } |
| |
| @Override |
| public int waitFor() throws InterruptedException { |
| return exitValue(); |
| } |
| |
| @Override |
| public int exitValue() { |
| return myExitValue; |
| } |
| |
| @Override |
| public void destroy() { |
| } |
| } |
| |
| private static class MyLogEntry { |
| final String myContent; |
| final String[] myDepIds; |
| |
| private MyLogEntry(@NotNull String content, @NotNull String[] depIds) { |
| myContent = content; |
| myDepIds = depIds; |
| } |
| } |
| } |