Skip to content

Commit

Permalink
Complete Android GradleSourceSet and ModelBuilder unit test.
Browse files Browse the repository at this point in the history
Changes:
- Removed unnecessary resource files from android-test project.
- Completed AndroidModelBuilder unit test in GradleApiConnectorTest.
- Suppressed and addressed minor warnings in DependencyCollector and SourceSetsModelBuilder.
- Removed classloader approach for retrieving artifact components in AndroidDependencyCollector.
- AndroidBuildVariant SourceSet now stores full task name for compileTask.
- AndroidSDK Jar is also added to AndroidBuildVariant SourceSet compileClasspath.
  • Loading branch information
Tanish-Ranjan committed Aug 21, 2024
1 parent 2cd01ff commit 9120f68
Show file tree
Hide file tree
Showing 23 changed files with 114 additions and 148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import com.microsoft.java.bs.gradle.model.impl.DefaultGradleSourceSet;
import com.microsoft.java.bs.gradle.model.impl.DefaultGradleSourceSets;
import com.microsoft.java.bs.gradle.plugin.dependency.DependencyCollector;
import org.jetbrains.annotations.NotNull;

/**
* The model builder for Gradle source sets.
Expand All @@ -50,8 +49,9 @@ public boolean canBuild(String modelName) {
return modelName.equals(GradleSourceSets.class.getName());
}

@SuppressWarnings("NullableProblems")
@Override
public @NotNull Object buildAll(@NotNull String modelName, @NotNull Project project) {
public Object buildAll(String modelName, Project project) {
// mapping Gradle source set to our customized model.
List<GradleSourceSet> sourceSets;

Expand Down Expand Up @@ -213,6 +213,7 @@ private <T extends Task> Set<T> tasksWithType(Project project, Class<T> clazz) {
* get all archive tasks for this project and maintain the archive file
* to source set mapping.
*/
@SuppressWarnings("deprecation")
private Map<File, List<File>> getArchiveOutputFiles(Project project, SourceSet sourceSet) {
// get all archive tasks for this project and find the dirs that are included in the archive
Set<AbstractArchiveTask> archiveTasks = tasksWithType(project, AbstractArchiveTask.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
import org.gradle.api.artifacts.result.ArtifactResult;
import org.gradle.api.artifacts.result.ComponentArtifactsResult;
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
import org.gradle.api.component.Component;
import org.gradle.api.specs.Specs;
import org.gradle.internal.component.external.model.ModuleComponentArtifactIdentifier;
import org.gradle.internal.component.local.model.ComponentFileArtifactIdentifier;
import org.gradle.internal.component.local.model.OpaqueComponentArtifactIdentifier;
import org.gradle.jvm.JvmLibrary;
import org.gradle.language.base.artifact.SourcesArtifact;
import org.gradle.language.java.artifact.JavadocArtifact;

Expand All @@ -29,10 +29,8 @@
import java.util.Set;
import java.util.stream.Collectors;

// WORK IN PROGRESS

/**
* TODO: JavaDoc.
* Collects dependencies from an Android build variant.
*/
public class AndroidDependencyCollector {

Expand All @@ -43,25 +41,22 @@ public class AndroidDependencyCollector {
*/
public static Set<GradleModuleDependency> getModuleDependencies(Project project, Object variant) {
Set<GradleModuleDependency> dependencies = new HashSet<>();
ClassLoader agpClassLoader = variant.getClass().getClassLoader();

try {
// Retrieve and process compile configuration
Configuration compileConfiguration =
(Configuration) AndroidUtils.getProperty(variant, "compileConfiguration");
dependencies.addAll(resolveConfigurationDependencies(
project,
compileConfiguration,
agpClassLoader)
compileConfiguration)
);

// Retrieve and process runtime configuration
Configuration runtimeConfiguration =
(Configuration) AndroidUtils.getProperty(variant, "runtimeConfiguration");
dependencies.addAll(resolveConfigurationDependencies(
project,
runtimeConfiguration,
agpClassLoader)
runtimeConfiguration)
);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
Expand All @@ -75,8 +70,7 @@ public static Set<GradleModuleDependency> getModuleDependencies(Project project,
*/
private static Set<GradleModuleDependency> resolveConfigurationDependencies(
Project project,
Configuration configuration,
ClassLoader agpClassLoader
Configuration configuration
) {
return configuration.getIncoming()
.artifactView(viewConfiguration -> {
Expand All @@ -86,24 +80,22 @@ private static Set<GradleModuleDependency> resolveConfigurationDependencies(
.getArtifacts()
.getArtifacts()
.stream()
.map(artifactResult -> getArtifact(project, artifactResult, agpClassLoader))
.map(artifactResult -> getArtifact(project, artifactResult))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}

private static DefaultGradleModuleDependency getArtifact(
Project project,
ResolvedArtifactResult artifactResult,
ClassLoader agpClassLoader
ResolvedArtifactResult artifactResult
) {
ComponentArtifactIdentifier id = artifactResult.getId();
File artifactFile = artifactResult.getFile();
if (id instanceof ModuleComponentArtifactIdentifier) {
return getModuleArtifactDependency(
project,
(ModuleComponentArtifactIdentifier) id,
artifactFile,
agpClassLoader
artifactFile
);
}
if (id instanceof OpaqueComponentArtifactIdentifier) {
Expand All @@ -115,54 +107,45 @@ private static DefaultGradleModuleDependency getArtifact(
return null;
}

@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "UnstableApiUsage"})
private static DefaultGradleModuleDependency getModuleArtifactDependency(
Project project,
ModuleComponentArtifactIdentifier artifactIdentifier,
File resolvedArtifactFile,
ClassLoader agpClassLoader
File resolvedArtifactFile
) {

try {
Class<? extends Component> androidLibClazz =
(Class<? extends Component>) agpClassLoader
.loadClass("com.android.build.api.variant.LibraryVariant");

ArtifactResolutionResult resolutionResult = project.getDependencies()
.createArtifactResolutionQuery()
.forComponents(artifactIdentifier.getComponentIdentifier())
.withArtifacts(
androidLibClazz /* componentType */,
JavadocArtifact.class, SourcesArtifact.class /*artifactTypes*/
)
.execute();

List<Artifact> artifacts = new LinkedList<>();
if (resolvedArtifactFile != null) {
artifacts.add(new DefaultArtifact(resolvedArtifactFile.toURI(), null));
}
ArtifactResolutionResult resolutionResult = project.getDependencies()
.createArtifactResolutionQuery()
.forComponents(artifactIdentifier.getComponentIdentifier())
.withArtifacts(
JvmLibrary.class /* componentType */,
JavadocArtifact.class, SourcesArtifact.class /*artifactTypes*/
)
.execute();

Set<ComponentArtifactsResult> resolvedComponents = resolutionResult.getResolvedComponents();
File sourceJar = getNonClassesArtifact(resolvedComponents, SourcesArtifact.class);
if (sourceJar != null) {
artifacts.add(new DefaultArtifact(sourceJar.toURI(), "sources"));
}
List<Artifact> artifacts = new LinkedList<>();
if (resolvedArtifactFile != null) {
artifacts.add(new DefaultArtifact(resolvedArtifactFile.toURI(), null));
}

File javaDocJar = getNonClassesArtifact(resolvedComponents, JavadocArtifact.class);
if (javaDocJar != null) {
artifacts.add(new DefaultArtifact(javaDocJar.toURI(), "javadoc"));
}
Set<ComponentArtifactsResult> resolvedComponents = resolutionResult.getResolvedComponents();
File sourceJar = getNonClassesArtifact(resolvedComponents, SourcesArtifact.class);
if (sourceJar != null) {
artifacts.add(new DefaultArtifact(sourceJar.toURI(), "sources"));
}

return new DefaultGradleModuleDependency(
artifactIdentifier.getComponentIdentifier().getGroup(),
artifactIdentifier.getComponentIdentifier().getModule(),
artifactIdentifier.getComponentIdentifier().getVersion(),
artifacts
);
} catch (ClassNotFoundException e) {
return null;
File javaDocJar = getNonClassesArtifact(resolvedComponents, JavadocArtifact.class);
if (javaDocJar != null) {
artifacts.add(new DefaultArtifact(javaDocJar.toURI(), "javadoc"));
}

return new DefaultGradleModuleDependency(
artifactIdentifier.getComponentIdentifier().getGroup(),
artifactIdentifier.getComponentIdentifier().getModule(),
artifactIdentifier.getComponentIdentifier().getVersion(),
artifacts
);

}

private static File getNonClassesArtifact(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package com.microsoft.java.bs.gradle.plugin.dependency;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
Expand All @@ -18,7 +19,6 @@
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier;
import org.gradle.api.artifacts.component.ComponentIdentifier;
import org.gradle.api.artifacts.result.ArtifactResolutionResult;
import org.gradle.api.artifacts.result.ArtifactResult;
import org.gradle.api.artifacts.result.ComponentArtifactsResult;
Expand Down Expand Up @@ -111,20 +111,18 @@ private static DefaultGradleModuleDependency getArtifact(Project project,
}

private static List<ResolvedArtifactResult> getConfigurationArtifacts(Configuration config) {
return config.getIncoming()
.artifactView(viewConfiguration -> {
viewConfiguration.lenient(true);
viewConfiguration.componentFilter(Specs.<ComponentIdentifier>satisfyAll());
})
.getArtifacts() // get ArtifactCollection from ArtifactView.
.getArtifacts() // get a set of ResolvedArtifactResult from ArtifactCollection.
.stream()
.collect(Collectors.toList());
return new ArrayList<>(config.getIncoming()
.artifactView(viewConfiguration -> {
viewConfiguration.lenient(true);
viewConfiguration.componentFilter(Specs.satisfyAll());
})
.getArtifacts() // get ArtifactCollection from ArtifactView.
.getArtifacts());
}

private static DefaultGradleModuleDependency getModuleArtifactDependency(Project project,
ModuleComponentArtifactIdentifier artifactIdentifier, File resolvedArtifactFile) {
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "UnstableApiUsage"})
ArtifactResolutionResult resolutionResult = project.getDependencies()
.createArtifactResolutionQuery()
.forComponents(artifactIdentifier.getComponentIdentifier())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.List;
import java.util.LinkedList;
import java.util.Set;
import java.util.stream.Collectors;

/**
* Utility class for android related operations.
Expand Down Expand Up @@ -126,7 +127,8 @@ private static GradleSourceSet convertVariantToGradleSourceSet(Project project,

// compile task in android (compileReleaseJavaWithJavac)
HashSet<String> tasks = new HashSet<>();
tasks.add("compile" + capitalize(variantName) + "JavaWithJavac");
String compileTaskName = "compile" + capitalize(variantName) + "JavaWithJavac";
tasks.add(SourceSetUtils.getFullTaskName(projectPath, compileTaskName));
gradleSourceSet.setTaskNames(tasks);

String projectName = SourceSetUtils.stripPathPrefix(projectPath);
Expand All @@ -136,7 +138,7 @@ private static GradleSourceSet convertVariantToGradleSourceSet(Project project,
String displayName = projectName + " [" + variantName + ']';
gradleSourceSet.setDisplayName(displayName);

// TODO: Set Module dependencies
// module dependencies
Set<GradleModuleDependency> moduleDependencies =
AndroidDependencyCollector.getModuleDependencies(project, variant);
gradleSourceSet.setModuleDependencies(moduleDependencies);
Expand Down Expand Up @@ -225,6 +227,20 @@ private static GradleSourceSet convertVariantToGradleSourceSet(Project project,
.getMethod("getCompileConfiguration").invoke(variant);
Set<File> classpathFiles = (Set<File>) compileConfig.getClass()
.getMethod("getFiles").invoke(compileConfig);
// add Android SDK
Object androidComponents = getAndroidComponentExtension(project);
if (androidComponents != null) {
Object sdkComponents = getProperty(androidComponents, "sdkComponents");
Object bootClasspath = ((Provider<?>) getProperty(sdkComponents, "bootclasspathProvider")).get();
try {
List<RegularFile> bootClasspathFiles = (List<RegularFile>) bootClasspath.getClass().getMethod("get").invoke(bootClasspath);
List<File> sdkClasspath = bootClasspathFiles.stream().map(RegularFile::getAsFile).collect(Collectors.toList());
classpathFiles.addAll(sdkClasspath);
} catch (IllegalStateException | InvocationTargetException e) {
// failed to retrieve android sdk classpath
// do nothing
}
}
// add R.jar file
String taskName = "process" + capitalize(variantName) + "Resources";
Task processResourcesTask = project.getTasks().findByName(taskName);
Expand Down Expand Up @@ -262,20 +278,37 @@ private static GradleSourceSet convertVariantToGradleSourceSet(Project project,
* @param project Gradle project to extract the AndroidExtension object.
*/
private static Object getAndroidExtension(Project project) {
return getExtension(project, "android");
}

/**
* Extracts the AndroidComponentsExtension from the given project.
*
* @param project Gradle project to extract the AndroidComponentsExtension object.
*/
private static Object getAndroidComponentExtension(Project project) {
return getExtension(project, "androidComponents");
}

/**
* Extracts the given extension from the given project.
*
* @param project Gradle project to extract the extension object.
* @param extensionName Name of the extension to extract.
*/
private static Object getExtension(Project project, String extensionName) {
Object extension = null;

try {
Object convention = project.getClass().getMethod("getConvention").invoke(project);
Object extensionMap = convention.getClass().getMethod("getAsMap").invoke(convention);
extension = extensionMap.getClass()
.getMethod("get", Object.class).invoke(extensionMap, "android");
.getMethod("get", Object.class).invoke(extensionMap, extensionName);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
// do nothing
}

return extension;

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.microsoft.java.bs.core.Launcher;
import com.microsoft.java.bs.core.internal.managers.PreferenceManager;
import com.microsoft.java.bs.core.internal.model.Preferences;
import com.microsoft.java.bs.gradle.model.GradleModuleDependency;
import com.microsoft.java.bs.gradle.model.GradleSourceSet;
import com.microsoft.java.bs.gradle.model.GradleSourceSets;
import com.microsoft.java.bs.gradle.model.ScalaExtension;
Expand Down Expand Up @@ -93,14 +94,29 @@ void testGetGradleSourceSets() {
}

@Test
void testGetAndroidSourceSets() {
void testAndroidSourceSets() {
// NOTE: Create a `local.properties` file in the android-test project
// and configure the `sdk.dir` property
File projectDir = projectPath.resolve("android-test").toFile();
PreferenceManager preferenceManager = new PreferenceManager();
preferenceManager.setPreferences(new Preferences());
GradleApiConnector connector = new GradleApiConnector(preferenceManager);
GradleSourceSets gradleSourceSets = connector.getGradleSourceSets(projectDir.toURI(), null);
assertEquals(4, gradleSourceSets.getGradleSourceSets().size());
// TODO: Verify if complete source sets were retrieved
findSourceSet(gradleSourceSets, "app [debug]");
findSourceSet(gradleSourceSets, "app [release]");
findSourceSet(gradleSourceSets, "mylibrary [debug]");
findSourceSet(gradleSourceSets, "mylibrary [release]");
Set<GradleModuleDependency> combinedModuleDependencies = new HashSet<>();
for (GradleSourceSet sourceSet : gradleSourceSets.getGradleSourceSets()) {
assertEquals(2, sourceSet.getSourceDirs().size());
assertEquals(4, sourceSet.getResourceDirs().size());
assertEquals(0, sourceSet.getExtensions().size());
assertEquals(0, sourceSet.getArchiveOutputFiles().size());
assertTrue(sourceSet.hasTests());
combinedModuleDependencies.addAll(sourceSet.getModuleDependencies());
}
assertEquals(87, combinedModuleDependencies.size());
}

private GradleSourceSet findSourceSet(GradleSourceSets gradleSourceSets, String displayName) {
Expand All @@ -110,7 +126,7 @@ private GradleSourceSet findSourceSet(GradleSourceSets gradleSourceSets, String
.orElse(null);
assertNotNull(sourceSet, () -> {
String availableSourceSets = gradleSourceSets.getGradleSourceSets().stream()
.map(ss -> ss.getDisplayName())
.map(GradleSourceSet::getDisplayName)
.collect(Collectors.joining(", "));
return "DisplayName not found " + displayName + ". Available: " + availableSourceSets;
});
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 9120f68

Please sign in to comment.