001/* 002 * Copyright (C) 2015-2021 KeepSafe Software 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.getkeepsafe.dexcount.report; 017 018import com.getkeepsafe.dexcount.CountReporter; 019import com.getkeepsafe.dexcount.PackageTree; 020import com.getkeepsafe.dexcount.colors.Color; 021import com.getkeepsafe.dexcount.colors.IOConsumer; 022import com.getkeepsafe.dexcount.colors.Styleable; 023import com.getkeepsafe.dexcount.thrift.TreeGenOutput; 024import com.microsoft.thrifty.KtApiKt; 025import com.microsoft.thrifty.protocol.Protocol; 026import com.microsoft.thrifty.transport.Transport; 027import okio.BufferedSource; 028import okio.GzipSource; 029import okio.Okio; 030import okio.Source; 031import org.gradle.api.logging.LogLevel; 032import org.gradle.workers.WorkAction; 033import org.jetbrains.annotations.NotNull; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037import java.io.File; 038import java.io.IOException; 039import java.io.PrintWriter; 040import java.io.Writer; 041 042public abstract class ReportOutputWorker implements WorkAction<ReportOutputWorkerParams> { 043 private static final Logger LOGGER = LoggerFactory.getLogger(ReportOutputWorker.class); 044 045 @Override 046 public void execute() { 047 try { 048 actuallyExecute(); 049 } catch (IOException e) { 050 LOGGER.error("Error reporting dexcount output; please clean and rebuild.", e); 051 } 052 } 053 054 private void actuallyExecute() throws IOException { 055 TreeGenOutput treeGen = readTreeGenFile(); 056 if (treeGen.tree == null) { 057 LOGGER.error("Corrupted dexcount data; please clean and rebuild."); 058 return; 059 } 060 061 String inputRepresentation = treeGen.inputRepresentation; 062 if (inputRepresentation == null) { 063 LOGGER.error("Corrupted dexcount data; please clean and rebuild."); 064 return; 065 } 066 067 PackageTree tree = PackageTree.fromThrift(treeGen.tree); 068 CountReporter reporter = new CountReporter( 069 tree, 070 getParameters().getVariantName().get(), 071 new Slf4jStyleable(LOGGER), 072 getParameters().getPrintOptions().get(), 073 inputRepresentation, 074 false); 075 076 reporter.report(); 077 } 078 079 private TreeGenOutput readTreeGenFile() throws IOException { 080 File file = getParameters().getPackageTreeFile().getAsFile().get(); 081 082 try ( 083 Source source = Okio.source(file); 084 GzipSource gzip = new GzipSource(source); 085 BufferedSource bufferedSource = Okio.buffer(gzip); 086 Transport transport = KtApiKt.transport(bufferedSource); 087 Protocol protocol = KtApiKt.compactProtocol(transport) 088 ) { 089 return TreeGenOutput.ADAPTER.read(protocol); 090 } 091 } 092 093 private static class Slf4jStyleable implements Styleable { 094 private final Logger logger; 095 096 Slf4jStyleable(Logger logger) { 097 this.logger = logger; 098 } 099 100 @Override 101 public void withStyledOutput(Color color, LogLevel level, IOConsumer<PrintWriter> fn) throws IOException { 102 LoggerWriterAdapter adapter = new LoggerWriterAdapter(logger, color, level); 103 try (PrintWriter pw = new PrintWriter(adapter)) { 104 fn.accept(pw); 105 pw.flush(); 106 } 107 } 108 } 109 110 private static class LoggerWriterAdapter extends Writer { 111 private final Logger logger; 112 private final Color color; 113 private final LogLevel logLevel; 114 115 LoggerWriterAdapter(Logger logger, Color color, LogLevel logLevel) { 116 this.logger = logger; 117 this.color = color; 118 this.logLevel = logLevel != null ? logLevel : LogLevel.WARN; 119 } 120 121 @Override 122 public void write(@NotNull char[] chars, int length, int offset) throws IOException { 123 String message = new String(chars, length, offset); 124 if (System.lineSeparator().equals(message)) { 125 // Dumb hack to work around PrintWriter.println breaking Gradle's stdout 126 // as mediated through SLF4J. 127 // 128 // Basically, this completely eliminates the newline from println calls 129 // and relies on SLF4J's (Gradle's?) behavior of appending the newline. 130 // 131 // Without this hack, one println results in three or four newlines. 132 return; 133 } 134 135 switch (logLevel) { 136 case DEBUG: 137 logger.debug(message); 138 break; 139 140 case INFO: 141 case LIFECYCLE: 142 logger.info(message); 143 break; 144 145 case WARN: 146 case QUIET: 147 logger.warn(message); 148 break; 149 150 case ERROR: 151 logger.error(message); 152 break; 153 } 154 } 155 156 @Override 157 public void flush() { 158 159 } 160 161 @Override 162 public void close() { 163 164 } 165 } 166}