Skip to content

Commit 5d3cc88

Browse files
authored
Add analytics during build and track kotlin usage (#1499)
1 parent 09f0e90 commit 5d3cc88

File tree

14 files changed

+282
-15
lines changed

14 files changed

+282
-15
lines changed

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ task copyFilesToProjectTemeplate {
298298
from "$TEST_APP_PATH/app/gradle-helpers/CustomExecutionLogger.gradle"
299299
into "$DIST_FRAMEWORK_PATH/app/gradle-helpers"
300300
}
301+
copy {
302+
from "$TEST_APP_PATH/app/gradle-helpers/AnalyticsCollector.gradle"
303+
into "$DIST_FRAMEWORK_PATH/app/gradle-helpers"
304+
}
301305
copy {
302306
from "$TEST_APP_PATH/app/gradle-helpers/BuildToolTask.gradle"
303307
into "$DIST_FRAMEWORK_PATH/app/gradle-helpers"

test-app/app/build.gradle

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import static org.gradle.internal.logging.text.StyledTextOutput.Style
3333
apply plugin: "com.android.application"
3434
apply from: "gradle-helpers/BuildToolTask.gradle"
3535
apply from: "gradle-helpers/CustomExecutionLogger.gradle"
36+
apply from: "gradle-helpers/AnalyticsCollector.gradle"
3637

3738
def enableKotlin = (project.hasProperty("useKotlin") && project.useKotlin == "true")
3839

@@ -82,6 +83,14 @@ def computeBuildToolsVersion = { ->
8283
project.hasProperty("buildToolsVersion") ? buildToolsVersion : "29.0.2"
8384
}
8485

86+
def enableAnalytics = (project.hasProperty("gatherAnalyticsData") && project.useKotlin == "true")
87+
def analyticsFilePath = "$rootDir/analytics/build-statistics.json"
88+
def analyticsCollector = project.ext.AnalyticsCollector.withOutputPath(analyticsFilePath)
89+
if (enableKotlin && enableAnalytics) {
90+
analyticsCollector.markHasUseKotlinPropertyInApp()
91+
analyticsCollector.writeAnalyticsFile()
92+
}
93+
8594
project.ext.selectedBuildType = project.hasProperty("release") ? "release" : "debug"
8695

8796
buildscript {
@@ -800,7 +809,14 @@ task buildMetadata(type: BuildToolTask) {
800809

801810
setOutputs outLogger
802811

803-
args "android-metadata-generator.jar"
812+
def paramz = new ArrayList<String>()
813+
paramz.add("android-metadata-generator.jar")
814+
815+
if(enableAnalytics){
816+
paramz.add("analyticsFilePath=$analyticsFilePath")
817+
}
818+
819+
args paramz.toArray()
804820
}
805821
}
806822

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import groovy.json.JsonBuilder
2+
3+
import java.nio.charset.StandardCharsets
4+
import java.nio.file.Files
5+
import java.nio.file.Paths
6+
import java.nio.file.Path
7+
8+
class AnalyticsCollector{
9+
10+
private final String analyticsFilePath
11+
private boolean hasUseKotlinPropertyInApp = false
12+
private boolean hasKotlinRuntimeClasses = false
13+
14+
private AnalyticsCollector(String analyticsFilePath){
15+
this.analyticsFilePath = analyticsFilePath
16+
}
17+
18+
static AnalyticsCollector withOutputPath(String analyticsFilePath){
19+
return new AnalyticsCollector(analyticsFilePath)
20+
}
21+
22+
void markHasUseKotlinPropertyInApp() {
23+
hasUseKotlinPropertyInApp = true
24+
}
25+
26+
void writeAnalyticsFile() {
27+
def jsonBuilder = new JsonBuilder()
28+
def kotlinUsageData = new Object()
29+
kotlinUsageData.metaClass.hasUseKotlinPropertyInApp = hasUseKotlinPropertyInApp
30+
kotlinUsageData.metaClass.hasKotlinRuntimeClasses = hasKotlinRuntimeClasses
31+
jsonBuilder(kotlinUsage: kotlinUsageData)
32+
def prettyJson = jsonBuilder.toPrettyString()
33+
34+
35+
36+
Path statisticsFilePath = Paths.get(analyticsFilePath)
37+
38+
if (Files.notExists(statisticsFilePath)) {
39+
Files.createDirectories(statisticsFilePath.getParent())
40+
Files.createFile(statisticsFilePath)
41+
}
42+
43+
Files.write(statisticsFilePath, prettyJson.getBytes(StandardCharsets.UTF_8))
44+
45+
}
46+
}
47+
48+
ext.AnalyticsCollector = AnalyticsCollector

test-app/build-tools/android-metadata-generator/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ repositories {
4343

4444
dependencies {
4545
compile 'org.apache.bcel:bcel:6.2'
46+
compile 'com.google.code.gson:gson:2.8.5'
4647
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-metadata-jvm', version: '0.1.0'
4748
compile files("./src/libs/dx.jar")
4849
}

test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/ClassDirectory.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package com.telerik.metadata;
22

3+
import com.telerik.metadata.analytics.AnalyticsCollector;
4+
import com.telerik.metadata.analytics.AnalyticsCollectorProvider;
35
import com.telerik.metadata.desc.ClassDescriptor;
6+
import com.telerik.metadata.kotlin.classes.KotlinClassMetadataParser;
7+
import com.telerik.metadata.kotlin.classes.impl.KotlinClassMetadataParserImpl;
8+
9+
import org.apache.bcel.classfile.ClassParser;
410

511
import java.io.File;
612
import java.io.IOException;
@@ -9,11 +15,11 @@
915
import java.util.List;
1016
import java.util.Map;
1117

12-
import org.apache.bcel.classfile.ClassParser;
13-
1418
public class ClassDirectory implements ClassMapProvider {
1519
private final String path;
1620
private final Map<String, ClassDescriptor> classMap;
21+
private static final KotlinClassMetadataParser kotlinClassMetadataParser = new KotlinClassMetadataParserImpl();
22+
private static final AnalyticsCollector analyticsCollector = AnalyticsCollectorProvider.getInstance().provideAnalyticsCollector();
1723
private static final String CLASS_EXT = ".class";
1824
private static final String DEX_EXT = ".dex";
1925

@@ -37,7 +43,7 @@ public static ClassDirectory readDirectory(String path) throws IOException {
3743
}
3844

3945
private static void readDirectory(ClassDirectory dir, String path)
40-
throws IOException {
46+
throws IOException {
4147
List<File> subDirs = new ArrayList<File>();
4248
File currentDir = new File(path);
4349
for (File file : currentDir.listFiles()) {
@@ -51,7 +57,7 @@ private static void readDirectory(ClassDirectory dir, String path)
5157
subDirs.add(file);
5258
}
5359
}
54-
for (File sd: subDirs) {
60+
for (File sd : subDirs) {
5561
readDirectory(dir, sd.getAbsolutePath());
5662
}
5763
}
@@ -61,10 +67,17 @@ private static ClassDescriptor getClassDescriptor(String name, File file) throws
6167
if (name.endsWith(CLASS_EXT)) {
6268
ClassParser cp = new ClassParser(file.getAbsolutePath());
6369
clazz = new com.telerik.metadata.bcl.ClassInfo(cp.parse());
70+
markIfKotlinClass(clazz);
6471
} else if (name.endsWith(DEX_EXT)) {
6572
// TODO:
6673
}
6774

6875
return clazz;
6976
}
77+
78+
private static void markIfKotlinClass(ClassDescriptor classDescriptor) {
79+
if (kotlinClassMetadataParser.wasKotlinClass(classDescriptor)) {
80+
analyticsCollector.markHasKotlinRuntimeClassesIfNotMarkedAlready();
81+
}
82+
}
7083
}

test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Generator.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.telerik.metadata;
22

3+
import com.telerik.metadata.analytics.AnalyticsConfiguration;
4+
35
import java.io.BufferedReader;
46
import java.io.File;
57
import java.io.FileInputStream;
@@ -8,18 +10,20 @@
810
import java.io.InputStreamReader;
911
import java.security.InvalidParameterException;
1012
import java.util.ArrayList;
11-
import java.util.Collections;
1213
import java.util.List;
1314

1415
public class Generator {
1516

16-
public static final String MDG_OUTPUT_DIR = "mdg-output-dir.txt";
17-
public static final String MDG_JAVA_DEPENDENCIES = "mdg-java-dependencies.txt";
17+
private static final String ANALYTICS_ARGUMENT_BEGINNING = "analyticsFilePath=";
18+
private static final String MDG_OUTPUT_DIR = "mdg-output-dir.txt";
19+
private static final String MDG_JAVA_DEPENDENCIES = "mdg-java-dependencies.txt";
1820

1921
/**
2022
* @param args
2123
*/
2224
public static void main(String[] args) {
25+
enableAnalyticsBasedOnArgs(args);
26+
2327
try {
2428
String metadataOutputDir;
2529
List<String> params;
@@ -54,6 +58,15 @@ public static void main(String[] args) {
5458
}
5559
}
5660

61+
private static void enableAnalyticsBasedOnArgs(String[] args){
62+
for (String arg : args) {
63+
if (arg.startsWith(ANALYTICS_ARGUMENT_BEGINNING)) {
64+
String filePath = arg.replace(ANALYTICS_ARGUMENT_BEGINNING, "");
65+
AnalyticsConfiguration.enableAnalytics(filePath);
66+
}
67+
}
68+
}
69+
5770
public static List<String> getFileRows(String filename) throws IOException {
5871
List<String> rows = new ArrayList<String>();
5972
BufferedReader br = null;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.telerik.metadata.analytics;
2+
3+
public interface AnalyticsCollector {
4+
5+
void markHasKotlinRuntimeClassesIfNotMarkedAlready();
6+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.telerik.metadata.analytics;
2+
3+
import com.telerik.metadata.analytics.impl.EnabledAnalyticsCollectorImpl;
4+
import com.telerik.metadata.analytics.impl.NoOpAnalyticsCollector;
5+
6+
public class AnalyticsCollectorProvider {
7+
private static final AnalyticsCollectorProvider ourInstance = new AnalyticsCollectorProvider();
8+
9+
public static AnalyticsCollectorProvider getInstance() {
10+
return ourInstance;
11+
}
12+
13+
private AnalyticsCollectorProvider() {
14+
}
15+
16+
public AnalyticsCollector provideAnalyticsCollector() {
17+
if (AnalyticsConfiguration.areAnalyticsEnabled()) {
18+
String analyticsFilePath = AnalyticsConfiguration.getAnalyticsFilePath();
19+
return new EnabledAnalyticsCollectorImpl(analyticsFilePath);
20+
} else {
21+
return new NoOpAnalyticsCollector();
22+
}
23+
}
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.telerik.metadata.analytics;
2+
3+
public final class AnalyticsConfiguration {
4+
private static String analyticsFilePath;
5+
private static boolean areAnalyticsEnabled;
6+
7+
private AnalyticsConfiguration(){}
8+
9+
public static void enableAnalytics(String analyticsJsonFilePath){
10+
analyticsFilePath = analyticsJsonFilePath;
11+
areAnalyticsEnabled = true;
12+
}
13+
14+
static boolean areAnalyticsEnabled(){
15+
return areAnalyticsEnabled;
16+
}
17+
18+
static String getAnalyticsFilePath(){
19+
return analyticsFilePath;
20+
}
21+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.telerik.metadata.analytics.impl;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.GsonBuilder;
5+
import com.google.gson.JsonElement;
6+
import com.google.gson.JsonObject;
7+
import com.telerik.metadata.analytics.AnalyticsCollector;
8+
9+
import java.nio.charset.StandardCharsets;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.nio.file.Paths;
13+
14+
public final class EnabledAnalyticsCollectorImpl implements AnalyticsCollector {
15+
16+
private static final String HAS_KOTLIN_RUNTIME_CLASSES_JSON_PROPERTY_NAME = "hasKotlinRuntimeClasses";
17+
private static final String HAS_USE_KOTLIN_PROPERTY_IN_APP_JSON_PROPERTY_NAME = "hasUseKotlinPropertyInApp";
18+
private static final String KOTLIN_USAGE_JSON_PROPERTY_NAME = "kotlinUsage";
19+
20+
21+
private final String analyticsFilePath;
22+
private final Gson gson;
23+
private boolean hasMarked;
24+
25+
public static void main(String... args) {
26+
EnabledAnalyticsCollectorImpl a = new EnabledAnalyticsCollectorImpl("/Users/vmutafov/work/android_runtime_release/android-runtime/test-app/analytics/build-statistics.json");
27+
a.markHasKotlinRuntimeClassesIfNotMarkedAlready();
28+
}
29+
30+
public EnabledAnalyticsCollectorImpl(String analyticsFilePath) {
31+
this.analyticsFilePath = analyticsFilePath;
32+
this.gson = new GsonBuilder().setPrettyPrinting().create();
33+
this.hasMarked = false;
34+
}
35+
36+
@Override
37+
public void markHasKotlinRuntimeClassesIfNotMarkedAlready() {
38+
if (!hasMarked) {
39+
40+
try {
41+
Path statisticsFilePath = Paths.get(analyticsFilePath);
42+
String json;
43+
44+
if (Files.notExists(statisticsFilePath)) {
45+
Files.createDirectories(statisticsFilePath.getParent());
46+
Files.createFile(statisticsFilePath);
47+
48+
JsonObject kotlinUsageObject = createNewKotlinUsageJsonObject(true);
49+
json = gson.toJson(kotlinUsageObject);
50+
} else {
51+
byte[] fileContent = Files.readAllBytes(statisticsFilePath);
52+
String statisticsJson = new String(fileContent, StandardCharsets.UTF_8);
53+
JsonElement modifiedStatisticsJson = modifyExistingAnalyticsJsonObject(statisticsJson, true);
54+
json = gson.toJson(modifiedStatisticsJson);
55+
}
56+
57+
Files.write(statisticsFilePath, json.getBytes(StandardCharsets.UTF_8));
58+
hasMarked = true;
59+
} catch (Exception e) {
60+
System.out.println(e.getMessage()); // do not fail the build if analytics collection fails
61+
}
62+
}
63+
}
64+
65+
private JsonObject createNewKotlinUsageJsonObject(boolean hasKotlinRuntimeClasses) {
66+
JsonObject jsonObject = new JsonObject();
67+
68+
JsonObject kotlinUsageObject = new JsonObject();
69+
kotlinUsageObject.addProperty(HAS_KOTLIN_RUNTIME_CLASSES_JSON_PROPERTY_NAME, hasKotlinRuntimeClasses);
70+
71+
jsonObject.add(KOTLIN_USAGE_JSON_PROPERTY_NAME, kotlinUsageObject);
72+
73+
return jsonObject;
74+
}
75+
76+
private JsonElement modifyExistingAnalyticsJsonObject(String json, boolean hasKotlinRuntimeClasses) {
77+
JsonElement jsonElement = new Gson().fromJson(json, JsonElement.class);
78+
JsonObject jsonObject = jsonElement.getAsJsonObject();
79+
80+
if (jsonObject.has(KOTLIN_USAGE_JSON_PROPERTY_NAME)) {
81+
JsonObject kotlinUsageObject = jsonObject.getAsJsonObject(KOTLIN_USAGE_JSON_PROPERTY_NAME);
82+
kotlinUsageObject.addProperty(HAS_KOTLIN_RUNTIME_CLASSES_JSON_PROPERTY_NAME, hasKotlinRuntimeClasses);
83+
84+
if(!kotlinUsageObject.has(HAS_USE_KOTLIN_PROPERTY_IN_APP_JSON_PROPERTY_NAME)){
85+
kotlinUsageObject.addProperty(HAS_USE_KOTLIN_PROPERTY_IN_APP_JSON_PROPERTY_NAME, false);
86+
}
87+
}
88+
89+
return jsonElement;
90+
}
91+
}

0 commit comments

Comments
 (0)