Я использую Eclipse и работаю с проектами Android.Я не могу использовать Android Studio по многим причинам.К сожалению, Eclipse не способен работать с архивами AAR.После долгой борьбы мы решили сделать проекты Eclipse НЕГРЕЙДНЫМИ (исключить характер gradle из всех проектов Eclipse) и подготовить список зависимостей с помощью специального скрипта gradle.Чтобы сделать возможной работу с Android, я написал следующий скрипт gradle, который выполняет следующее:
- поиск всех зависимостей во всех проектах
copyзависимости от кэша gradle до специального проекта Eclipse "jars-from-gradle"
составляют ".classpath" для всех проектов, чтобы использовать только найденные библиотеки
Вот скрипт gradle:
apply plugin: "eclipse"
configurations {
eclipseOnly {
description = 'this is used only to build eclipse classpath'
}
}
afterEvaluate{
project.tasks['eclipseProject'].dependsOn(project.tasks['cleanEclipseProject'])
project.tasks['eclipseClasspath'].dependsOn(project.tasks['cleanEclipseClasspath'])
eclipse{
File f = rootProject.ext.find("explodedAarsDir");
if(f == null) {
f = new File("${rootProject.projectDir}/jars-from-gradle/explodedAars/");
rootProject.ext.set("explodedAarsDir", f)
f.mkdirs();
}
f = rootProject.ext.find("globalDependenciesRepo");
if(f == null) {
f = new File("${rootProject.projectDir}/jars-from-gradle/libs");
rootProject.ext.set("globalDependenciesRepo", f)
f.mkdirs();
}
org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory frf = rootProject.ext.find("fileReferenceFactory");
if(frf == null) {
frf = new org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory();
rootProject.ext.set("fileReferenceFactory", frf)
}
if(!rootProject.ext.has("eclipseFileMapping")) {
rootProject.ext.set("eclipseFileMapping", new HashMap<File, File>())
}
Map<File, File> eclipseFileMapping = rootProject.ext.get("eclipseFileMapping")
eclipseFileMapping.put(new File("bin/main"), new File("bin"))
eclipseFileMapping.put(new File("bin/test"), new File("bin"))
eclipseFileMapping.put(new File("${buildDir}/classes/java/main"), new File("${projectDir.parentFile}/${project.name}/bin"))
eclipseFileMapping.put(new File("${buildDir}/classes/java/test"), new File("${projectDir.parentFile}/${project.name}/bin"))
eclipseFileMapping.put(new File("${buildDir}/resources/main"), new File("${projectDir.parentFile}/${project.name}/bin"))
eclipseFileMapping.put(new File("${buildDir}/resources/test"), new File("${projectDir.parentFile}/${project.name}/bin"))
configurations {
eclipsePlusConfig {
description = "files to include into eclipse classpath"
transitive = false
}
eclipseMinusConfig {
description = "files to exclude from eclipse classpath"
transitive = false
}
}
project{
setupEclipseProject()
}
classpath{
defaultOutputDir = new File("${projectDir}/bin")
plusConfigurations += [project.configurations.eclipseOnly]
if(project.extensions.findByName("android") != null) {
plusConfigurations += [project.configurations.compile]
plusConfigurations += [project.configurations.runtime]
project.eclipse.project{
natures 'org.eclipse.andmore.AndroidNature'
buildCommands.clear()
buildCommand "org.eclipse.andmore.ResourceManagerBuilder"
buildCommand "org.eclipse.andmore.PreCompilerBuilder"
buildCommand "org.eclipse.jdt.core.javabuilder"
buildCommand "org.eclipse.andmore.ApkBuilder"
containers 'org.eclipse.andmore.DEPENDENCIES', 'org.eclipse.andmore.LIBRARIES', 'org.eclipse.andmore.ANDROID_FRAMEWORK'
}
} else {
plusConfigurations += [project.configurations.compile]
plusConfigurations += [project.configurations.runtime]
}
file {
beforeMerged { classpath ->
eclipse.classpath.sourceSets.each {
println " source set "+ it.getName()
}
eclipse.classpath.plusConfigurations.each{ processConf(it, " ", "plus conf: ") }
eclipse.classpath.minusConfigurations.each{ processConf(it, " ", "minus conf: ") }
eclipse.classpath.plusConfigurations.add(project.configurations['eclipsePlusConfig'])
eclipse.classpath.minusConfigurations.add(project.configurations['eclipseMinusConfig'])
}
whenMerged { classpath ->
List<org.gradle.plugins.ide.eclipse.model.ClasspathEntry> replacementEclipseClasspath = createEclipseReplacementClasspath(classpath);
classpath.setEntries(replacementEclipseClasspath)
}
withXml { n ->
Set<File> existingPaths = new HashSet<File>();
def rootNode = n.asNode();
for(int nodeIndex = 0; nodeIndex<rootNode.children().size(); nodeIndex++) {
def chld = rootNode.children().get(nodeIndex);
if("classpathentry".equals(chld.name())) {
removeGradleAttributes(chld);
chld.attributes().remove("output");
String kind = chld.attributes().get("kind");
for(Map.Entry entry : chld.attributes().entrySet()) {
if("path".equals(entry.key) || "sourcepath".equals(entry.key)) {
f = new File(entry.value);
if(f.toPath().isAbsolute()) {
String relativeName = rootProject.projectDir.toPath().relativize(f.toPath()).toString();
entry.value = "/"+ relativeName.replace('\\', '/');
}
if("path".equals(entry.key) && existingPaths.contains(f)) {
rootNode.children().remove(nodeIndex--);
break;
}
if(entry.value.startsWith("/")) {
if("src".equals(kind)) {
chld.attributes().put("combineaccessrules", "false");
}
}
existingPaths.add(f);
}
}
if("lib".equals(kind)) {
chld.attributes().put("exported", "true");
}
}
}
}
}
}
task prepareEclipse{
doFirst{
mkDirIfNotExists(new File("${projectDir}/src/main/java"))
mkDirIfNotExists(new File("${projectDir}/src/main/resources"))
mkDirIfNotExists(new File("${projectDir}/src/test/java"))
mkDirIfNotExists(new File("${projectDir}/src/test/resources"))
}
}
tasks['eclipseClasspath'].dependsOn(prepareEclipse)
}
}
List<org.gradle.plugins.ide.eclipse.model.ClasspathEntry> createEclipseReplacementClasspath(org.gradle.plugins.ide.eclipse.model.Classpath eclipseClasspath) {
Map<String, org.gradle.plugins.ide.eclipse.model.ClasspathEntry> replacementEclipseClasspathAsMap = new HashMap<String, org.gradle.plugins.ide.eclipse.model.ClasspathEntry>();
eclipseClasspath.entries.each{ clspthentry ->
dumpClassPathEntry(clspthentry)
if (clspthentry instanceof org.gradle.plugins.ide.eclipse.model.Library) {
org.gradle.plugins.ide.eclipse.model.Library library = clspthentry;
String moduleId = library.getModuleVersion().toString();
String groupId = null;
String artifactId = null;
String artifactVersion = null;
int index = moduleId.indexOf(":");
if(index >= 0) {
groupId = moduleId.substring(0, index);
String tmp = moduleId.substring(++index);
index = tmp.indexOf(":")
if(index >= 0) {
artifactId = tmp.substring(0, index);
artifactVersion = tmp.substring(++index);
}
}
moduleId = moduleId.replaceAll(":", "-");
println(" classpath entry found: moduleId="+ moduleId);
if (library.getPath().endsWith(".aar")) {
explodeAarJarFiles(moduleId, groupId, artifactId, artifactVersion, library, replacementEclipseClasspathAsMap);
} else {
copyLibraryFromGradleCache(moduleId, groupId, artifactId, artifactVersion, library, replacementEclipseClasspathAsMap)
}
} else {
replacementEclipseClasspathAsMap.put(clspthentry.kind+ "_"+ clspthentry.path, clspthentry);
}
}
List<org.gradle.plugins.ide.eclipse.model.ClasspathEntry> replacementEclipseClasspath = new ArrayList<org.gradle.plugins.ide.eclipse.model.ClasspathEntry>();
replacementEclipseClasspath.addAll(replacementEclipseClasspathAsMap.values());
List<String> KINDS = new ArrayList<String>();
KINDS.add('src');
KINDS.add('con');
KINDS.add('lib');
KINDS.add('output');
Collections.sort(replacementEclipseClasspath, new Comparator<org.gradle.plugins.ide.eclipse.model.ClasspathEntry>() {
private int detectKindIndex(String entryKind) {
for(int i = 0; i<KINDS.size(); i++) {
if(KINDS[i].equals(entryKind)) {
return i;
}
}
return KINDS.size();
}
public int compare(org.gradle.plugins.ide.eclipse.model.ClasspathEntry entry1, org.gradle.plugins.ide.eclipse.model.ClasspathEntry entry2) {
int kindDiff = detectKindIndex(entry1.getKind()) - detectKindIndex(entry2.getKind());
if(kindDiff != 0) {
return kindDiff;
}
if(entry1 instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency) {
if(!(entry2 instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency)) {
return 11;
}
} else if(entry2 instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency) {
return -1;
}
return entry1.path.compareTo(entry2.path);
}
});
return replacementEclipseClasspath;
}
void copyLibraryFromGradleCache(String moduleId, String groupId, String artifactId, String artifactVersion, org.gradle.plugins.ide.eclipse.model.Library library, Map<String, org.gradle.plugins.ide.eclipse.model.Library> replacementEclipseClasspathAsMap) {
String artifactIdAndVersion = artifactId + "-"+ artifactVersion;
int fileSuffixIndex = -1;
if(artifactId != null) {
fileSuffixIndex = library.getPath().lastIndexOf(artifactIdAndVersion);
}
if(fileSuffixIndex >= 0) {
fileSuffixIndex += artifactIdAndVersion.length();
} else {
fileSuffixIndex = library.getPath().lastIndexOf(".");
}
if(moduleId == null || fileSuffixIndex <= 0) {
println(" non-movable library found: "+ library.getPath())
replacementEclipseClasspathAsMap.put(moduleId, library);
} else {
File targetGroupFolder = null;
if (groupId==null || groupId.trim().length()==0) {
targetGroupFolder = new File(globalDependenciesRepo.getAbsolutePath());
} else {
targetGroupFolder = new File(globalDependenciesRepo.getAbsolutePath(), groupId);
if(!targetGroupFolder.exists()){
targetGroupFolder.mkdirs()
}
}
String fileSuffix = library.getPath().substring(fileSuffixIndex);
String targetFileName = artifactIdAndVersion;
println(" target filename: "+ targetGroupFolder+ " -> "+ targetFileName)
java.nio.file.Path targetFile = java.nio.file.Paths.get(targetGroupFolder.getAbsolutePath(), targetFileName + fileSuffix);
java.nio.file.Path sourceFile = java.nio.file.Paths.get(library.getPath());
if(sourceFile.toFile().exists() && !sourceFile.toFile().isDirectory()) {
java.nio.file.Files.copy(sourceFile, targetFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES);
library.setPath(targetFile.toString());
}
if(library.getSourcePath() != null) {
java.nio.file.Path sourceSourceFile = java.nio.file.Paths.get(library.getSourcePath().getPath());
if(sourceFile.toFile().exists() && !sourceFile.toFile().isDirectory()) {
java.nio.file.Path sourceTargetFile = java.nio.file.Paths.get(targetGroupFolder.getAbsolutePath(), targetFileName + "_source"+ fileSuffix);
println(" copying source file: "+ sourceSourceFile + " into "+ sourceTargetFile);
java.nio.file.Files.copy(sourceSourceFile, sourceTargetFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES);
//println( " TROLOLO "+ rootProject.projectDir.toPath().relativize(sourceTargetFile) );
library.setSourcePath(fileReferenceFactory.fromFile(sourceTargetFile.toFile()));
}
}
replacementEclipseClasspathAsMap.put(moduleId + "_"+ targetFileName + fileSuffix, library);
}
}
void explodeAarJarFiles(String moduleId, String groupId, String artifactId, String artifactVersion, org.gradle.plugins.ide.eclipse.model.Library aarLibrary, Map<String, org.gradle.plugins.ide.eclipse.model.Library> replacementEclipseClasspathAsMap) {
File aarFile = new File(aarLibrary.getPath());
println(" exploding AAR dependency: "+ aarFile.getAbsolutePath());
File targetFolder = new File(explodedAarsDir, moduleId);
println(" target folder: "+ targetFolder.getAbsolutePath());
if (targetFolder.exists()) {
println(" target folder exists. deleting ");
project.delete(files(targetFolder))
}
if (!targetFolder.mkdirs()) {
throw new RuntimeException("Cannot create folder: ${targetFolder.getAbsolutePath()}");
}
try {
if(aarLibrary.getSourcePath() != null) {
java.nio.file.Path sourceSourceFile = java.nio.file.Paths.get(aarLibrary.getSourcePath().getPath());
if(sourceSourceFile.toFile().exists() && !sourceSourceFile.toFile().isDirectory()) {
String sourceFileExt = sourceSourceFile.toString();
int extensionIndex = sourceFileExt.lastIndexOf(".");
if(extensionIndex >= 0) {
sourceFileExt = sourceFileExt.substring(extensionIndex);
} else {
sourceFileExt = ".jar";
}
java.nio.file.Path sourceTargetFile = java.nio.file.Paths.get(targetFolder.toString(), moduleId+ "_source"+ sourceFileExt);
println(" copying source file: "+ sourceSourceFile + " into "+ sourceTargetFile);
java.nio.file.Files.copy(sourceSourceFile, sourceTargetFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES);
aarLibrary.setSourcePath(fileReferenceFactory.fromFile(sourceTargetFile.toFile()));
}
}
java.util.zip.ZipFile zipFile = new java.util.zip.ZipFile(aarFile);
zipFile.entries().each{ fileInsideAar ->
if (fileInsideAar.getName().endsWith(".jar")) {
String targetName = moduleId+ "_"+ fileInsideAar.getName().replace('/', '_').replace('\\', '_');
println(" jar inside aar: "+ fileInsideAar.getName());
println(" copying to: "+ targetName);
File targetFile = new File(targetFolder, targetName);
int index = 1;
while (targetFile.exists()) {
targetFile = new File(targetFolder, format("${targetName}_${++index}"));
}
try {
InputStream inputStream = zipFile.getInputStream(fileInsideAar);
java.nio.file.Files.copy(inputStream, targetFile.toPath());
org.gradle.plugins.ide.eclipse.model.Library library = new org.gradle.plugins.ide.eclipse.model.Library(fileReferenceFactory.fromFile(targetFile));
library.setSourcePath(aarLibrary.getSourcePath())
replacementEclipseClasspathAsMap.put(targetFile.getName(), library);
} catch (IOException e) {
throw new RuntimeException(
"Cannot write entry to file: ${e.getMessage()}: ${targetFile.getAbsolutePath()}", e);
}
}
};
} catch (IOException e) {
throw new RuntimeException(
"Cannot explode aar: ${e.getMessage()}: ${aarFile.getAbsolutePath()}", e);
}
}
void removeGradleAttributes(Node node) {
for(int i = 0; i<node.children().size(); i++) {
Node attrs = node.children().get(i);
if("attributes".equals(attrs.name())) {
for(int j = 0; j<attrs.children().size(); j++) {
Node attr = attrs.children().get(j);
boolean isGradleAttr = false;
for(Map.Entry entry : attr.attributes().entrySet()) {
if(entry.key.toLowerCase().contains("gradle") || entry.value.toLowerCase().contains("gradle")) {
isGradleAttr = true;
}
}
if(isGradleAttr) {
attrs.remove(attr);
j--;
}
}
if(attrs.children().size()==0) {
node.remove(attrs);
}
}
}
}
void mkDirIfNotExists(File file) {
if(!file.exists()) {
file.mkdir()
}
}
void processConf(org.gradle.api.internal.artifacts.configurations.DefaultConfiguration cnf, String startIndent, String prefix) {
println(startIndent + prefix + cnf.name+ ", path: "+ cnf.path)
StringBuilder indent = new StringBuilder();
for(int i = 0; i<startIndent.length(); i++) {
indent.append(" ");
}
indent.append(" ");
cnf.dependencies.each{ dep ->
maskDependencyIfNeeded(indent.toString(), dep)
}
cnf.allDependencies.each{ dep ->
maskDependencyIfNeeded(indent.toString(), dep)
}
}
void maskDependencyIfNeeded(String indent, org.gradle.api.internal.artifacts.dependencies.AbstractDependency dep) {
if(dep instanceof org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency) {
boolean needToExcludeDep = false;
Set<File> maskedDepFiles = new HashSet<File>();
dep.files.each{ depFile ->
File f = findMaskedFile(depFile, null, true);
if(f != null) {
maskedDepFiles.add(f)
needToExcludeDep = true;
println(indent.toString()+ " mask dep file "+ depFile+ " -> "+ f)
}
}
if(needToExcludeDep) {
project.configurations['eclipseMinusConfig'].dependencies.add(dep)
if(!maskedDepFiles.isEmpty()) {
org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency newDep = new org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency(dep.targetComponentId, project.files(maskedDepFiles))
project.configurations['eclipsePlusConfig'].dependencies.add(newDep)
}
}
}
}
File findMaskedFile(File f, String postfix, boolean initiallyUnmasked) {
if(f != null) {
for(Map.Entry<File, File> efm : eclipseFileMapping) {
boolean masked = false;
if(initiallyUnmasked) {
if(efm.key.equals(f)) {
masked = true;
}
} else {
if(efm.value.equals(f)) {
masked = true;
}
}
if(masked) {
if(postfix != null) {
return new File(efm.value, postfix)
} else {
return efm.value;
}
}
}
return findMaskedFile(f.parentFile, postfix==null ? f.name : f.name + File.pathSeparator+ postfix, initiallyUnmasked);
}
return null;
}
void dumpClassPathEntry(org.gradle.plugins.ide.eclipse.model.ClasspathEntry clspthentry) {
if("output".equals(clspthentry.kind)) {
// the clspthentry is instance of org.gradle.plugins.ide.eclipse.model.Output
println(" output: "+ clspthentry.path)
} else if("src".equals(clspthentry.kind)) {
if(clspthentry instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency) {
// the clspthentry is instance of org.gradle.plugins.ide.eclipse.model.ProjectDependency
println(" project: exported="+ clspthentry.exported+ "; path="+ clspthentry.path)
} else {
// the clspthentry is instance of org.gradle.plugins.ide.eclipse.model.SourceFolder
println(" src folder: "+ clspthentry.name+ " ("+ clspthentry.dir+ ") -> output: "+ clspthentry.output)
if(clspthentry.excludes != null && clspthentry.excludes.size()>0) {
println(" excludes:")
clspthentry.excludes.each{ excl ->
println(" "+ excl)
}
}
if(clspthentry.includes != null && clspthentry.includes.size()>0) {
println(" includes:")
clspthentry.includes.each{ incl ->
println(" "+ incl)
}
}
}
} else if("con".equals(clspthentry.kind)) {
//the clspthentry is instance of org.gradle.plugins.ide.eclipse.model.Container
println(" con: exported="+ clspthentry.exported+ "; path="+ clspthentry.path)
} else if("lib".equals(clspthentry.kind)) {
//the clspthentry is instance of org.gradle.plugins.ide.eclipse.model.Library
println(" lib: file="+ clspthentry.library.path)
} else {
println(" UNKNOWN "+ clspthentry.kind+ " -> "+ clspthentry.getClass().getName())
}
}
// Gradle adds all custom sourceSets to eclipse's linkedResources. We do not need them in eclipse project, but we do not understand how and when the source is linked.
// So, JUST HACK IT: clear the linked resourcces after evaluating the project!
// But gradle is such a misterious thing! just clearing does not help. We need to put something there
// so lets put the existing linked resource, but with relative path :(
void setupEclipseProject() {
if(project.name.contains("-android")) {
project.eclipse.project{
linkedResource name: 'AndroidManifest.xml', type: '1', location: 'PROJECT_LOC/src/main/AndroidManifest.xml'
linkedResource name: 'android-java', type: '2', location: 'PARENT-1-PROJECT_LOC/assets/build/android/java'
linkedResource name: 'res', type: '2', location: 'PROJECT_LOC/src/main/res'
}
}
}
Я знаю, что это не вершина программирования gradle, но он просто работает.Основная проблема этого скрипта в том, что он ТРЕБУЕТ, чтобы все зависимости были типа 'compile', но для Android это устарело.Новые типы зависимостей - «api» и «реализация».Теперь 'compile' просто устарела, но я боюсь, что он вообще исчезнет.
Существует простое и уродливое решение: дублируйте все нестандартные зависимости с типом зависимости 'eclipseOnly'.Это решение используется в приведенном выше сценарии.Это работает, но действительно ужасно, потому что мы должны модифицировать скрипты gradle во всех проектах.Но моя цель не касаться всех проектов.Теперь сценарий содержится в отдельном файле 'eclipseHelper.gradle' и включен в корневой проект следующим образом:
subprojects{
apply from: "${rootProject.projectDir}/eclipseHelper.gradle"
}
Я пытаюсь добиться добавления определенных типов зависимостей Android в Eclipse.Во-первых, я исключил все варианты 'release' из зависимостей:
def androidExtension = project.extensions.findByName("android")
if (androidExtension != null) {
android.variantFilter { variant ->
def names = variant.flavors*.name
def buildTypeName = variant.buildType.name
// if buildtype is required for filtering use
// the above field
if (variant.name.contains("elease")) {
variant.ignore = true
}
}
}
Во-вторых, я попытался добавить конфигурации вариантов Android в путь к классу Eclipse ('plusConfigurations'):
def androidExtension = project.extensions.findByName("android")
if (androidExtension != null) {
boolean applicationBuild = rootProject.hasProperty("applicationBuild")
if (androidExtension.getClass().getName().contains("LibraryExtension")){
android.libraryVariants.all { variant ->
eclipse.classpath.plusConfigurations += variant.compileConfiguration
eclipse.classpath.plusConfigurations += variant.runtimeConfiguration
}
} else {
android.applicationVariants.all { variant ->
eclipse.classpath.plusConfigurations += variant.compileConfiguration
eclipse.classpath.plusConfigurations += variant.runtimeConfiguration
}
}
}
У нас есть несколько библиотек Android среди наших проектов, и эти библиотеки построены с различными вариантами.Поэтому я получил следующее исключение:
org.gradle.internal.component.AmbiguousVariantSelectionException: More than one variant of project :proj1-android matches the consumer a
ttributes:
- Configuration ':proj1-android:debugApiElements' variant android-aidl:
- Found artifactType 'android-aidl' but wasn't required.
- Required com.android.build.api.attributes.BuildTypeAttr 'debug' and found compatible value 'debug'.
- Found com.android.build.api.attributes.VariantAttr 'debug' but wasn't required.
- Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' and found compatible value 'Aar'.
- Required org.gradle.usage 'java-api' and found compatible value 'java-api'.
- ...
- ...
- Configuration ':proj1-android:debugApiElements' variant jar:
- Found artifactType 'jar' but wasn't required.
- Required com.android.build.api.attributes.BuildTypeAttr 'debug' and found compatible value 'debug'.
- Found com.android.build.api.attributes.VariantAttr 'debug' but wasn't required.
- Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' and found compatible value 'Aar'.
- Required org.gradle.usage 'java-api' and found compatible value 'java-api'.
Может ли кто-нибудь помочь мне принять какое-либо из решений?
- выбрать любую конфигурацию («отладку») из набора
- полностью пропустить все конфигурации подпроекта и включить его в .classpath в качестве зависимости проекта
Заранее спасибо, Андрей