Skip to content

Commit eab54ce

Browse files
authored
add gradle tasks for metalava generation (#671)
* add gradle tasks for metalava generation * Address all comments and have the tasks running * fix merge conflict * add commands to ci * add pygithub dependency * update commit * update api txt * add fireci commands * remove the unwanted task * google java format * address all comments * update commands * remove unneccasry baselines * small updates
1 parent 137fb97 commit eab54ce

File tree

19 files changed

+1374
-1
lines changed

19 files changed

+1374
-1
lines changed

buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/FirebaseLibraryPlugin.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,27 @@
1515
package com.google.firebase.gradle.plugins;
1616

1717
import com.android.build.gradle.LibraryExtension;
18+
import com.android.build.gradle.api.AndroidSourceSet;
19+
import com.android.build.gradle.api.LibraryVariant;
1820
import com.google.common.collect.ImmutableList;
1921
import com.google.common.collect.ImmutableMap;
22+
import com.google.firebase.gradle.plugins.apiinfo.GenerateApiTxtFileTask;
23+
import com.google.firebase.gradle.plugins.apiinfo.ApiInformationTask;
2024
import com.google.firebase.gradle.plugins.ci.device.FirebaseTestServer;
25+
26+
import java.util.Collection;
27+
import org.codehaus.groovy.util.ReleaseInfo;
2128
import org.gradle.api.Plugin;
2229
import org.gradle.api.Project;
30+
import org.gradle.api.artifacts.Configuration;
31+
import org.gradle.api.attributes.Attribute;
32+
import org.gradle.api.file.FileCollection;
2333
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
34+
import java.io.File;
35+
import java.nio.file.Paths;
36+
import java.util.ArrayList;
37+
import java.util.List;
38+
import java.util.stream.Collectors;
2439

2540
public class FirebaseLibraryPlugin implements Plugin<Project> {
2641

@@ -54,6 +69,10 @@ public void apply(Project project) {
5469
}
5570
});
5671
}
72+
if (System.getenv().containsKey("METALAVA_BINARY_PATH")) {
73+
setupApiInfomrationAnalysis(project, android);
74+
}
75+
5776

5877
android.testServer(new FirebaseTestServer(project, firebaseLibrary.testLab));
5978

@@ -71,6 +90,49 @@ public void apply(Project project) {
7190
ImmutableList.of("-module-name", kotlinModuleName(project))));
7291
}
7392

93+
private static void setupApiInfomrationAnalysis(Project project, LibraryExtension android) {
94+
95+
String metalavaBinaryPath = System.getenv("METALAVA_BINARY_PATH");
96+
AndroidSourceSet mainSourceSet = android.getSourceSets().getByName("main");
97+
File outputFile = project.getRootProject().file(Paths.get(
98+
project.getRootProject().getBuildDir().getPath(),
99+
"apiinfo",
100+
project.getPath().substring(1).replace(":", "_")));
101+
File outputApiFile = new File(outputFile.getAbsolutePath() + "_api.txt");
102+
String sourcePathArgument = mainSourceSet.getJava().getSrcDirs().stream()
103+
.map(File::getAbsolutePath)
104+
.collect(Collectors.joining(":"));
105+
if(mainSourceSet.getJava().getSrcDirs().stream().noneMatch(File::exists)) {
106+
return;
107+
}
108+
109+
project.getTasks().register("apiInformation", ApiInformationTask.class, task -> {
110+
task.setApiTxt(project.file("api.txt"));
111+
task.setMetalavaBinaryPath(metalavaBinaryPath);
112+
task.setSourcePath(sourcePathArgument);
113+
task.setOutputFile(outputFile);
114+
task.setBaselineFile(project.file("baseline.txt"));
115+
task.setOutputApiFile(outputApiFile);
116+
if (project.hasProperty("updateBaseline")) {
117+
task.setUpdateBaseline(true);
118+
} else {
119+
task.setUpdateBaseline(false);
120+
}
121+
});
122+
123+
project.getTasks().register("generateApiTxtFile", GenerateApiTxtFileTask.class, task -> {
124+
task.setApiTxt(project.file("api.txt"));
125+
task.setMetalavaBinaryPath(metalavaBinaryPath);
126+
task.setSourcePath(sourcePathArgument);
127+
task.setBaselineFile(project.file("baseline.txt"));
128+
if (project.hasProperty("updateBaseline")) {
129+
task.setUpdateBaseline(true);
130+
} else {
131+
task.setUpdateBaseline(false);
132+
}
133+
});
134+
}
135+
74136
private static void setupStaticAnalysis(
75137
Project project, LibraryExtension android, FirebaseLibraryExtension library) {
76138
project.afterEvaluate(
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.gradle.plugins.apiinfo;
16+
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
import java.util.stream.Collector;
20+
import java.util.stream.Collectors;
21+
import java.util.stream.Stream;
22+
import org.gradle.api.DefaultTask;
23+
import org.gradle.api.GradleException;
24+
import org.gradle.api.tasks.Input;
25+
import org.gradle.api.tasks.InputFile;
26+
import org.gradle.api.tasks.OutputFile;
27+
import org.gradle.api.tasks.TaskAction;
28+
29+
import java.io.File;
30+
import java.io.FileNotFoundException;
31+
import java.io.FileOutputStream;
32+
import java.util.Arrays;
33+
34+
/**
35+
Task generates the api diff of the current source code against the api.txt file stored
36+
alongside the project's src directory.
37+
*/
38+
public abstract class ApiInformationTask extends DefaultTask {
39+
40+
@Input
41+
abstract String getMetalavaBinaryPath();
42+
43+
@InputFile
44+
abstract File getApiTxt();
45+
46+
@Input
47+
abstract String getSourcePath();
48+
49+
@OutputFile
50+
abstract File getBaselineFile();
51+
52+
@OutputFile
53+
abstract File getOutputApiFile();
54+
55+
@Input
56+
abstract boolean getUpdateBaseline();
57+
58+
@OutputFile
59+
abstract File getOutputFile();
60+
61+
public abstract void setSourcePath(String value);
62+
63+
public abstract void setBaselineFile(File value);
64+
65+
public abstract void setUpdateBaseline(boolean value);
66+
67+
public abstract void setMetalavaBinaryPath(String value);
68+
69+
public abstract void setApiTxt(File value);
70+
71+
public abstract void setOutputApiFile(File value);
72+
73+
public abstract void setOutputFile(File value);
74+
75+
76+
@TaskAction
77+
void execute() {
78+
File outputFileDir = getOutputFile().getParentFile();
79+
if(!outputFileDir.exists()) {
80+
outputFileDir.mkdirs();
81+
}
82+
83+
// Generate api.txt file and store it in the build directory.
84+
getProject().exec(spec-> {
85+
spec.setCommandLine(Arrays.asList(
86+
getMetalavaBinaryPath(),
87+
"--source-path", getSourcePath(),
88+
"--api", getOutputApiFile().getAbsolutePath(),
89+
"--format=v2"
90+
));
91+
spec.setIgnoreExitValue(true);
92+
});
93+
getProject().exec(spec-> {
94+
List<String> cmd = new ArrayList<>(Arrays.asList(
95+
getMetalavaBinaryPath(),
96+
"--source-files", getOutputApiFile().getAbsolutePath(),
97+
"--check-compatibility:api:current", getApiTxt().getAbsolutePath(),
98+
"--format=v2",
99+
"--no-color",
100+
"--delete-empty-baselines"
101+
));
102+
if(getUpdateBaseline()) {
103+
cmd.addAll(Arrays.asList("--update-baseline", getBaselineFile().getAbsolutePath()));
104+
} else if(getBaselineFile().exists()) {
105+
cmd.addAll(Arrays.asList("--baseline", getBaselineFile().getAbsolutePath()));
106+
}
107+
spec.setCommandLine(cmd);
108+
spec.setIgnoreExitValue(true);
109+
try {
110+
spec.setStandardOutput(new FileOutputStream(getOutputFile()));
111+
} catch (FileNotFoundException e) {
112+
throw new GradleException("Unable to run the command", e);
113+
}
114+
});
115+
116+
}
117+
118+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.gradle.plugins.apiinfo;
16+
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import org.gradle.api.DefaultTask;
21+
import org.gradle.api.tasks.Input;
22+
import org.gradle.api.tasks.OutputFile;
23+
import org.gradle.api.tasks.TaskAction;
24+
import java.io.File;
25+
26+
import java.util.Arrays;
27+
28+
29+
public abstract class GenerateApiTxtFileTask extends DefaultTask {
30+
31+
@Input
32+
abstract String getMetalavaBinaryPath();
33+
34+
@OutputFile
35+
abstract File getApiTxt();
36+
37+
@Input
38+
abstract String getSourcePath();
39+
40+
41+
@OutputFile
42+
abstract File getBaselineFile();
43+
44+
@Input
45+
abstract boolean getUpdateBaseline();
46+
47+
48+
public abstract void setSourcePath(String value);
49+
50+
public abstract void setBaselineFile(File value);
51+
52+
public abstract void setUpdateBaseline(boolean value);
53+
54+
public abstract void setMetalavaBinaryPath(String value);
55+
56+
public abstract void setApiTxt(File value);
57+
58+
@TaskAction
59+
void execute() {
60+
List<String> cmd = new ArrayList<String>(Arrays.asList(
61+
getMetalavaBinaryPath(),
62+
"--source-path", getSourcePath(),
63+
"--api", getApiTxt().getAbsolutePath(),
64+
"--format=v2",
65+
"--delete-empty-baselines"
66+
));
67+
68+
if(getUpdateBaseline()) {
69+
cmd.addAll(Arrays.asList("--update-baseline", getBaselineFile().getAbsolutePath()));
70+
} else if(getBaselineFile().exists()) {
71+
cmd.addAll(Arrays.asList("--baseline", getBaselineFile().getAbsolutePath()));
72+
}
73+
74+
getProject().exec(spec -> {
75+
spec.setCommandLine(cmd);
76+
spec.setIgnoreExitValue(true);
77+
});
78+
79+
}
80+
}

ci/fireci/fireci/commands.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import click
1616
import os
1717

18+
from github import Github
19+
1820
from . import gradle
1921
from . import ci_command
2022
from . import stats
@@ -61,3 +63,38 @@ def smoke_tests(app_build_variant, test_apps_dir):
6163
os.path.join(cwd, 'build', 'm2repository')),
6264
workdir=location,
6365
)
66+
67+
68+
@click.option('--issue_number', 'issue_number', required=True)
69+
@click.option('--repo_name', 'repo_name', required=True)
70+
@click.option('--auth_token', 'auth_token', required=True)
71+
@ci_command()
72+
def api_information(auth_token, repo_name, issue_number):
73+
"""Comments the api information on the pr"""
74+
75+
gradle.run('apiInformation')
76+
dir_suffix = 'build/apiinfo'
77+
comment_string = ""
78+
for filename in os.listdir(dir_suffix):
79+
subproject = filename
80+
formatted_output_lines = []
81+
with open(os.path.join(dir_suffix, filename), 'r') as f:
82+
outputlines = f.readlines()
83+
for line in outputlines:
84+
if 'error' in line:
85+
formatted_output_lines.append(line[line.find('error:'):])
86+
elif 'warning' in line:
87+
formatted_output_lines.append(line[line.find('warning:'):])
88+
89+
if formatted_output_lines:
90+
comment_string += 'The public api surface has changed for the subproject {}:\n'.format(subproject)
91+
comment_string += ''.join(formatted_output_lines)
92+
comment_string += '\n\n'
93+
if comment_string:
94+
comment_string += ('Please update the api.txt files for the subprojects being affected by this change '
95+
'with the files present in the artifacts directory. Also perform a major/minor bump accordingly.\n')
96+
# Comment to github.
97+
github_client = Github(auth_token)
98+
repo = github_client.get_repo(repo_name)
99+
pr = repo.get_pull(int(issue_number))
100+
pr.create_issue_comment(comment_string)

ci/fireci/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
'click==7.0',
2929
'opencensus==0.2.0',
3030
'google-cloud-monitoring==0.31.1',
31+
'PyGithub==1.43.8'
3132
],
3233
packages=find_packages(exclude=['tests']),
3334
entry_points={

firebase-abt/api.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Signature format: 2.0

0 commit comments

Comments
 (0)