From 4217aab93322b0c76f7db9618bbb44041ef6c084 Mon Sep 17 00:00:00 2001 From: Fi5t Date: Tue, 26 Jan 2021 21:31:12 +0300 Subject: [PATCH] fix: new gradle export (#1095) (PR #1097) * Update export of gradle project * Fix hardcoded index * Add versionCode and versionName to the export template --- .../main/java/jadx/api/JadxDecompiler.java | 19 ++- .../jadx/core/export/ApplicationParams.java | 39 +++++++ .../jadx/core/export/ExportGradleProject.java | 109 ++++++++++++++++-- .../resources/export/app.build.gradle.tmpl | 38 ++++++ .../main/resources/export/build.gradle.tmpl | 37 ++---- .../resources/export/settings.gradle.tmpl | 2 + .../tests/functional/TemplateFileTest.java | 6 +- 7 files changed, 211 insertions(+), 39 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/export/ApplicationParams.java create mode 100644 jadx-core/src/main/resources/export/app.build.gradle.tmpl create mode 100644 jadx-core/src/main/resources/export/settings.gradle.tmpl diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 14f7db9a4..cfd072b85 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -39,6 +39,7 @@ import jadx.core.export.ExportGradleProject; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.xmlgen.BinaryXMLParser; +import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResourcesSaver; /** @@ -193,7 +194,23 @@ public final class JadxDecompiler implements Closeable { File sourcesOutDir; File resOutDir; if (args.isExportAsGradleProject()) { - ExportGradleProject export = new ExportGradleProject(root, args.getOutDir()); + ResourceFile androidManifest = resources.stream() + .filter(resourceFile -> resourceFile.getType() == ResourceType.MANIFEST) + .findFirst() + .orElseThrow(IllegalStateException::new); + + ResContainer strings = resources.stream() + .filter(resourceFile -> resourceFile.getType() == ResourceType.ARSC) + .findFirst() + .orElseThrow(IllegalStateException::new) + .loadContent() + .getSubFiles() + .stream() + .filter(resContainer -> resContainer.getFileName().contains("strings.xml")) + .findFirst() + .orElseThrow(IllegalStateException::new); + + ExportGradleProject export = new ExportGradleProject(root, args.getOutDir(), androidManifest, strings); export.init(); sourcesOutDir = export.getSrcOutDir(); resOutDir = export.getResOutDir(); diff --git a/jadx-core/src/main/java/jadx/core/export/ApplicationParams.java b/jadx-core/src/main/java/jadx/core/export/ApplicationParams.java new file mode 100644 index 000000000..c78628351 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/export/ApplicationParams.java @@ -0,0 +1,39 @@ +package jadx.core.export; + +public class ApplicationParams { + + private final String applicationLabel; + private final Integer minSdkVersion; + private final Integer targetSdkVersion; + private final Integer versionCode; + private final String versionName; + + public ApplicationParams(String applicationLabel, Integer minSdkVersion, Integer targetSdkVersion, Integer versionCode, + String versionName) { + this.applicationLabel = applicationLabel; + this.minSdkVersion = minSdkVersion; + this.targetSdkVersion = targetSdkVersion; + this.versionCode = versionCode; + this.versionName = versionName; + } + + public String getApplicationName() { + return applicationLabel; + } + + public Integer getMinSdkVersion() { + return minSdkVersion; + } + + public Integer getTargetSdkVersion() { + return targetSdkVersion; + } + + public Integer getVersionCode() { + return versionCode; + } + + public String getVersionName() { + return versionName; + } +} diff --git a/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java b/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java index d8bc4c04b..0a55b2930 100644 --- a/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java +++ b/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java @@ -2,18 +2,28 @@ package jadx.core.export; import java.io.File; import java.io.IOException; +import java.io.StringReader; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import jadx.api.ResourceFile; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; +import jadx.core.xmlgen.ResContainer; public class ExportGradleProject { @@ -24,39 +34,62 @@ public class ExportGradleProject { "BuildConfig")); private final RootNode root; - private final File outDir; + private final File projectDir; + private final File appDir; private final File srcOutDir; private final File resOutDir; + private final ApplicationParams applicationParams; - public ExportGradleProject(RootNode root, File outDir) { + public ExportGradleProject(RootNode root, File projectDir, ResourceFile androidManifest, ResContainer appStrings) { this.root = root; - this.outDir = outDir; - this.srcOutDir = new File(outDir, "src/main/java"); - this.resOutDir = new File(outDir, "src/main"); + this.projectDir = projectDir; + this.appDir = new File(projectDir, "app"); + this.srcOutDir = new File(appDir, "src/main/java"); + this.resOutDir = new File(appDir, "src/main"); + this.applicationParams = getApplicationParams( + parseAndroidManifest(androidManifest), + parseAppStrings(appStrings)); } public void init() { try { FileUtils.makeDirs(srcOutDir); FileUtils.makeDirs(resOutDir); - saveBuildGradle(); + saveProjectBuildGradle(); + saveApplicationBuildGradle(); + saveSettingsGradle(); skipGeneratedClasses(); } catch (Exception e) { throw new JadxRuntimeException("Gradle export failed", e); } } - private void saveBuildGradle() throws IOException { + private void saveProjectBuildGradle() throws IOException { TemplateFile tmpl = TemplateFile.fromResources("/export/build.gradle.tmpl"); + tmpl.save(new File(projectDir, "build.gradle")); + } + + private void saveSettingsGradle() throws IOException { + TemplateFile tmpl = TemplateFile.fromResources("/export/settings.gradle.tmpl"); + + tmpl.add("applicationName", applicationParams.getApplicationName()); + tmpl.save(new File(projectDir, "settings.gradle")); + } + + private void saveApplicationBuildGradle() throws IOException { + TemplateFile tmpl = TemplateFile.fromResources("/export/app.build.gradle.tmpl"); String appPackage = root.getAppPackage(); + if (appPackage == null) { appPackage = "UNKNOWN"; } + tmpl.add("applicationId", appPackage); - // TODO: load from AndroidManifest.xml - tmpl.add("minSdkVersion", 9); - tmpl.add("targetSdkVersion", 21); - tmpl.save(new File(outDir, "build.gradle")); + tmpl.add("minSdkVersion", applicationParams.getMinSdkVersion()); + tmpl.add("targetSdkVersion", applicationParams.getTargetSdkVersion()); + tmpl.add("versionCode", applicationParams.getVersionCode()); + tmpl.add("versionName", applicationParams.getVersionName()); + tmpl.save(new File(appDir, "build.gradle")); } private void skipGeneratedClasses() { @@ -69,6 +102,60 @@ public class ExportGradleProject { } } + private ApplicationParams getApplicationParams(Document androidManifest, Document appStrings) { + Element manifest = (Element) androidManifest.getElementsByTagName("manifest").item(0); + Element usesSdk = (Element) androidManifest.getElementsByTagName("uses-sdk").item(0); + Element application = (Element) androidManifest.getElementsByTagName("application").item(0); + + Integer versionCode = Integer.valueOf(manifest.getAttribute("android:versionCode")); + String versionName = manifest.getAttribute("android:versionName"); + Integer minSdk = Integer.valueOf(usesSdk.getAttribute("android:minSdkVersion")); + Integer targetSdk = Integer.valueOf(usesSdk.getAttribute("android:targetSdkVersion")); + String appName = "UNKNOWN"; + + String appLabelName = application.getAttribute("android:label").split("/")[1]; + NodeList strings = appStrings.getElementsByTagName("string"); + + for (int i = 0; i < strings.getLength(); i++) { + String stringName = strings.item(i) + .getAttributes() + .getNamedItem("name") + .getNodeValue(); + + if (stringName.equals(appLabelName)) { + appName = strings.item(i).getTextContent(); + break; + } + } + + return new ApplicationParams(appName, minSdk, targetSdk, versionCode, versionName); + } + + private Document parseXml(String xmlContent) { + try { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = builder.parse(new InputSource(new StringReader(xmlContent))); + + document.getDocumentElement().normalize(); + + return document; + } catch (Exception e) { + throw new JadxRuntimeException("Can not parse xml content", e); + } + } + + private Document parseAppStrings(ResContainer appStrings) { + String content = appStrings.getText().getCodeStr(); + + return parseXml(content); + } + + private Document parseAndroidManifest(ResourceFile androidManifest) { + String content = androidManifest.loadContent().getText().getCodeStr(); + + return parseXml(content); + } + public File getSrcOutDir() { return srcOutDir; } diff --git a/jadx-core/src/main/resources/export/app.build.gradle.tmpl b/jadx-core/src/main/resources/export/app.build.gradle.tmpl new file mode 100644 index 000000000..bd6e36813 --- /dev/null +++ b/jadx-core/src/main/resources/export/app.build.gradle.tmpl @@ -0,0 +1,38 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.2" + + defaultConfig { + applicationId '{{applicationId}}' + minSdkVersion {{minSdkVersion}} + targetSdkVersion {{targetSdkVersion}} + versionCode {{versionCode}} + versionName "{{versionName}}" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + lintOptions { + abortOnError false + } +} + +dependencies { + // some dependencies +} diff --git a/jadx-core/src/main/resources/export/build.gradle.tmpl b/jadx-core/src/main/resources/export/build.gradle.tmpl index 483ab5804..c95e445df 100644 --- a/jadx-core/src/main/resources/export/build.gradle.tmpl +++ b/jadx-core/src/main/resources/export/build.gradle.tmpl @@ -1,35 +1,20 @@ buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' - } -} -apply plugin: 'com.android.application' - -repositories { - mavenCentral() - jcenter() -} - -android { - compileSdkVersion 23 - buildToolsVersion '23.0.1' - - defaultConfig { - applicationId '{{applicationId}}' - minSdkVersion {{minSdkVersion}} - targetSdkVersion {{targetSdkVersion}} - versionCode 1 - versionName "1.0" - } - - lintOptions { - abortOnError false + classpath 'com.android.tools.build:gradle:4.0.0' } } -dependencies { - // some dependencies +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir } diff --git a/jadx-core/src/main/resources/export/settings.gradle.tmpl b/jadx-core/src/main/resources/export/settings.gradle.tmpl new file mode 100644 index 000000000..62d298d44 --- /dev/null +++ b/jadx-core/src/main/resources/export/settings.gradle.tmpl @@ -0,0 +1,2 @@ +include ':app' +rootProject.name = '{{applicationName}}' diff --git a/jadx-core/src/test/java/jadx/tests/functional/TemplateFileTest.java b/jadx-core/src/test/java/jadx/tests/functional/TemplateFileTest.java index b6e39144f..81d615428 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/TemplateFileTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/TemplateFileTest.java @@ -11,14 +11,18 @@ public class TemplateFileTest { @Test public void testBuildGradle() throws Exception { - TemplateFile tmpl = TemplateFile.fromResources("/export/build.gradle.tmpl"); + TemplateFile tmpl = TemplateFile.fromResources("/export/app.build.gradle.tmpl"); tmpl.add("applicationId", "SOME_ID"); tmpl.add("minSdkVersion", 1); tmpl.add("targetSdkVersion", 2); + tmpl.add("versionCode", 3); + tmpl.add("versionName", "1.2.3"); String res = tmpl.build(); System.out.println(res); assertThat(res, containsString("applicationId 'SOME_ID'")); assertThat(res, containsString("targetSdkVersion 2")); + assertThat(res, containsString("versionCode 3")); + assertThat(res, containsString("versionName \"1.2.3\"")); } }