Strings with the same content doesn't work properly
efraespada opened this issue · 49 comments
With two strings with the same content, only the first one is obfuscated. The second returns junk chars.
StringCareTest.zip
Since v2.2
it has been patched, but this issue needs work
Is this fixed? I've noticed there is a new version...
Not yet
Oh ok.
Thank you.
Please update me when it gets fixed.
@AndroidDeveloperLB check out v3.0
.
This issue should be solved.
Seems to be fixed according to my tests.
However, when I use the library on the large project, I get this:
More than one file was found with OS independent path 'META-INF/library_release.kotlin_module'
Please explain why this occurs when using this repository.
EDIT:
I added this to the app's gradle file:
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/ASL2.0'
exclude 'META-INF/library_release.kotlin_module'
}
But then I get this:
AGPBI: {"kind":"error","text":"Program type already present: org.apache.commons.lang3.SerializationException","sources":[{}],"tool":"D8"}
> Task :app:transformClassesAndResourcesWithR8For...Release FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:transformClassesAndResourcesWithR8For...Release'.
> com.android.tools.r8.CompilationFailedException: Compilation failed to complete
I tried to delete all "build" folders. Also tried to invalidate cache. Didn't help.
I think this type of message means an external dependency is being added multiple times. This article in StackOverflow talks about it:
https://stackoverflow.com/questions/49676155/what-does-program-type-already-present-mean/49767860
In our case:
{
"kind": "error",
"text": "Program type already present: org.apache.commons.lang3.SerializationException",
"sources": [{}],
"tool": "D8"
}
You should solve it by implementing the library excluding that package:
implementation ("com.stringcare:library:$stringcare_version") {
exclude group: 'org.apache.commons' module: 'lang3'
}
Or:
implementation ("com.stringcare:library:$stringcare_version") {
exclude group: 'org.apache.commons' module: 'commons-lang3'
}
I haven´t tested it.
I tried adding this:
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/library_release.kotlin_module'
}
And also this:
implementation("com.stringcare:library:$stringcare_version") {
exclude group: 'org.apache.commons', module: 'lang3'
exclude group: 'commons-io', module: 'commons-io'
}
And I ran this:
./gradlew clean cleanBuildCache :app:assembleDebug
Still I have the same issue:
AGPBI: {"kind":"error","text":"Program type already present: org.apache.commons.lang3.SerializationException","sources":[{}],"tool":"D8"}
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:transformClassesAndResourcesWithR8ForCalleridRelease'.
> com.android.tools.r8.CompilationFailedException: Compilation failed to complete
I just noticed that I'm on "release" build. When switching to "debug" build, I can see more information. There is this conflict:
The only thing that is related to commons-io
on the project is this one:
api 'commons-io:commons-io:2.6'
And it's the latest stable version:
I'm seeing the correct module name (with conflict) is commons-lang3
, so you sould use:
implementation("com.stringcare:library:$stringcare_version") {
exclude group: 'org.apache.commons', module: 'commons-lang3'
}
Now it finally builds, but then I have a new issue. As soon as I call SC.reveal(R.string.something)
, I get this crash:
java.lang.ExceptionInInitializerError
at com....$1.run(....java:31)
Caused by: java.lang.NumberFormatException: For input string: "..."
at java.lang.Integer.parseInt(Integer.java:615)
at java.lang.Integer.parseInt(Integer.java:650)
at com.stringcare.library.CPlusLogic$Companion.revealV3(CPlusLogic.kt:128)
at com.stringcare.library.SC$Companion.reveal(SC.kt:124)
at com.stringcare.library.SC$Companion.reveal(SC.kt:107)
at com.stringcare.library.SC$Companion.reveal(SC.kt:87)
at com.stringcare.library.SC.reveal(Unknown Source:2)
...
So I think it still can't handle strings as the native string handling on Android.
I think this should be posted on a new issue though. It's not related to the original issue we talked about here.
Looks like the value of R.string.something
string is ...
, and that's wrong.
Analyze your APK for ensuring the plugin obfuscates strings: Build
> Analyze APK
You can test it too by calling getString(R.string.something)
and check what returns.
Should return something like: 123, -4, 45, 67, -56, 3, 27..
It's not really "...". I changed it because it's a private key :)
Putting this string into the sample I've made, it works perfectly fine.
However, when using the Analyze APK
on the large app, I don't see just numbers. I see the original string.
I think it's a configuration problem and StringCare plugin doesn't find valid resource files.
By default, SC looks for strings in every
strings.xml
file insidesrc\main
folder of your module.
Is your string located on the default folder/file?
I mean the "default" string resource is src\main\res\values{any_locale}\strings.xml
.
If your resource file is not called strings.xml
you'll have to configure StringCare for reading more file names.
Also If your string resource is located on a different source folder, configure it.
src\other_source_folder\res\values{any_locale}\strings.xml
Check out the Configuration page for more details.
That's true. Strings files are sent to translators, so it's bad to send them the keys too.
We have the special strings in a different file: "keys.xml".
However, we had it already set up on the previous versions of the library, as such:
stringcare {
// debug true
modules {
app {
stringFiles = ['strings.xml', "keys.xml"]
srcFolders = ['src/main']
}
}
}
It's not correct anymore ?
You use a Windows platform and the srcFolders
array could be breaking the compilation.
I mean:
stringcare {
modules {
app {
stringFiles = ['strings.xml', "keys.xml"]
srcFolders = ['src/main'] <- windows uses \\ (double slash) while macos uses /
}
}
}
Try without the srcFolders
var, the default config for the source folder should be enougth:
stringcare {
modules {
app {
stringFiles = ['strings.xml', "keys.xml"]
}
}
}
You can try too:
stringcare {
modules {
app {
stringFiles = ['strings.xml', "keys.xml"]
srcFolders = ['src\\main']
}
}
}
We use both MacOS and Windows.
And it's not true that Windows can't use "/". And it's on gradle, so it has its own language.
Also, as I wrote, it worked fine on the old version of Stringcare that we used before .
But I've tried just for seeing if it helps.
Removing the srcFolders = ['src/main']
or replacing it with the srcFolders = ['src\\main']
line causes the app not to be built, showing me these errors:
Have you tried this?
srcFolders = ["src\\main"] <-- this should work
srcFolders = ["src\main"]
srcFolders = ["src/main"]
Note the double quotes "
instead of '
I don't know what's happening.
The KotlinSample
project uses a similar configuration:
stringcare {
debug true
main_module "app"
modules {
app {
stringFiles = ['strings.xml', "strings_extra.xml"]
srcFolders = ["src\\main", "src\\other_source"]
}
}
}
And it works fine in Windows.
I noticed I had to delete manually the build
folder (clean
task doesn't remove all files and subfolders)
I confirm this configuration works on macOS and Windows:
apply plugin: StringCare
stringcare {
modules {
app {
stringFiles = ["strings.xml", "strings_extra.xml"]
srcFolders = ["src${File.separator}main", "src${File.separator}other_source"]
}
}
}
Ensure you delete the build folder completely.
OK so I used this the next lines and deleted all "build" folders manually after closing the IDE :
stringcare {
modules {
app {
stringFiles = ["strings.xml", "keys.xml"]
srcFolders = ["src${File.separator}main"]
}
}
}
And I got this error (after running the IDE again and trying to build&run the app) :
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:mergeSyncmeappDebugResources'.
> org.codehaus.groovy.runtime.GStringImpl cannot be cast to java.lang.String
And if I use the next thing again, but with deletion of "build" folders, it still crashes :
stringcare {
// debug true
modules {
app {
stringFiles = ["strings.xml", "keys.xml"]
srcFolders = ["src/main"]
}
}
}
I'm working on path treatment.
Meanwhile, you can try calling toString()
method over the text template:
"src${File.separator}main".toString()
There is a new release (v3.1)
- You can use the unix separator char
/
on Windows too.
apply plugin: StringCare
stringcare {
modules {
app {
stringFiles = ["strings.xml","more_strings.xml"]
srcFolders = ["src/main", "src/other_variant_source"]
}
library {
srcFolders = ['src/other_folder/main']
}
}
}
- Added two Gradle tasks for tracing and solving possible bugs or configurations. Checkout the Task page.
I still get the same Android resource linking failed
errors , whether I use "src${File.separator}main".toString()
or srcFolders = ["src/main"]
.
I tried to delete all "build" folders too. Didn't help.
The 0.9 version could handle the paths fine. Are you sure that's the cause to the issue?
What do you want to do with the tasks you've pointed to, exactly? I think such a thing should be contacted via email. The app isn't mine. It's of my job.
I'm not sure because on my projects always works and pass all the tests (on Windows and macOS).
The new tasks shows too much info about the obfuscation process (located files for the given configuration, fingerprint, obfuscation test..) and should help you to find what fails.
You can send it to my mail.
Remember to add a variant with the stringcareTestObfuscate${variant}
task.
I know. The POC works fine too. I have no idea why this doesn't work on the large app.
The link doesn't say about adding stringcareTestObfuscate variant.
Do you mean adding something like that:
productFlavors {
stringcareTestObfuscate {
//stuff here, same as on the normal app
}
}
stringcareTestObfuscate{$variant} task
Do you mean adding something like that?
No, no. It works as the build
Gradle task:
// this would be the main task that builds all variants
gradlew.bat build
// this would be the task that builds the debug variant
gradlew.bat buildDebug
// this would be the task that builds the firstflavorSecondflavorDebug variant
gradlew.bat buildFirstflavorSecondflavorDebug
If you run gradlew.bat signingReport
you will see all the available variants for your project.
stringcarePreview task
I recomend to run this task too. It doesn't need any variant and could be helpful for solving the problem, or at least to notice what's wrong.
You can see an output sample of both task in the wiki section.
So how do I add one for StringCare exactly?
Please just explain what to do.
I told you where it's explained:
There is a new release (v3.1)
..
- Added two Gradle tasks for tracing and solving possible bugs or configurations. Check out the Task page.
I thought you read it.
I've read it, but I don't understand why I don't see the task on the IDE, so I thought that maybe I need to add the task.
Please explain.
Should I just run a command?
Yes. Run the command on terminal, in the project's directory.
OK I've sent you via email
You only have sent the stringcarePreview
task report, and seems to be ok. Files and strings are found.
Whats shows the stringcareTestObfuscate${variant}
task?
Choose an existing BuildVariant for this value:
Ex:
$ gradlew.bat stringcareTestObfuscateDebug
You can write me this on email...
Anyway, sent again, this time with the output of this command.
Found how to fix it, but it doesn't mean the issue isn't here.
I've fixed it by removing the part that lets StringCare handle "strings.xml" file.
The reason:
We don't even need it to scan this file. Only "keys.xml".
So what I have is this:
apply plugin: StringCare
stringcare {
modules {
app {
stringFiles = [ "keys.xml"]
srcFolders = ["src/main"]
}
}
}
Now it works fine, I think.
The reason the issue exists, though, is that the library ruins the resources of "strings.xml" files, even though there is no hidden="true"
anywhere in any of those files.
My guess is that it happens when there are translations being used. Not just one "strings.xml" file.
I've noticed that it still can have issues.
We have 2 kinds of versions for the app (using build-variant), and StringCare didn't return the correct value of one of the strings in "keys.xml" correctly on the other version, so I get Gibberish instead.
I tried this (based on : https://github.com/StringCare/AndroidLibrary/wiki/Configuration#additional-resource-folders ) :
stringcare {
modules {
app {
stringFiles = ["keys.xml"]
srcFolders = ["src/main","src/the_other_app_variant"]
}
}
}
so I tried to rename the "keys.xml" file into something else on the other app, and tried again using this method. Still couldn't deal with it.
Note that there are keys on the extra app that are overriding the main app.
So what I think is that those issues are connected. Meaning that if there is a need to handle multiple files or strings from different files, it doesn't work correctly.
I'll review why it fails with your strings.xml
file.
About the other issue you mentioned, if I understood, do have you something like this? (for example)
1º src/main/.../keys.xml
:
<resources>
<string name="grievous" hidden="true">General Kenobi</string>
</resources>
2º src/the_other_app_variant/.../keys.xml
:
<resources>
<string name="grievous" hidden="true">General Kenobi 2</string>
</resources>
Yes, the files are are such:
app\src\main\res\values\keys.xml
And:
app\src\the_other_app_variant\res\values\keys.xml
With the following configuration:
sourceSets {
main.res.srcDirs = ['src/main/res', 'src/other_source/res']
}
I get the normal error response for duplicating resources:
[string/grievous] C:\Users\..\KotlinSample\app\src\other_source\res\values\strings_extra.xml:
Error: Duplicate resources
And the project can't compile.
Why did you configure sourceSets
? It should work fine without it.
I already wrote it's a different variant of the same app. The second variant should use the strings with priority of itself. Meaning that in the example you wrote, the string of grievous
will be General Kenobi 2
and not the other one.
You have to configure the sourceSets
.
src/main/.../keys.xml
:
<resources>
<string name="grievous" hidden="true">General Kenobi</string>
</resources>
src/the_other_app_variant/.../keys.xml
:
<resources>
<string name="kenobi" hidden="true">Hello There</string>
</resources>
With no configuration you will get this error because in code you use R.string.kenobi
and it's not included in the default folder src/main/res
:
error: failed linking file resources.
Android Studio (at compilation time) only knows the default configuration, which is the following:
sourceSets {
main.res.srcDirs = ['src/main/res']
}
You can define multiple sourceSets
configurations for different flavors
and buildTypes
:
sourceSets {
productionFlavor {
main.res.srcDirs = ['src/main/res', 'src/production/res']
}
developmentFlavor {
main.res.srcDirs = ['src/main/res', 'src/dev/res']
}
}
But not repeat the same resource identifier or you will get this:
Error: Duplicate resources
Check out the official documentation for more details:
https://developer.android.com/studio/build/build-variants#configure-sourcesets
That is incorrect. Both apps worked fine for years. The resources are shared between them, and one overrides the other. In addition, as I wrote before, StringCare worked fine with both apps on v0.9.
It even says it's automatic on the link you gave:
By default, Android Studio creates the main/ source set and directories for everything you want to share between all your build variants. However, you can create new source sets to control exactly what files Gradle compiles and packages for specific build types, product flavors (and combinations of product flavors when using flavor dimensions), and build variants.
There are no issues of "Duplicate resources" , not when we used v0.9 of StringCare, and not when we didn't use it at all.
Can you please explain what you plan that I should try, exactly?
You should try to read the documentation better xD
That is incorrect. Both apps worked fine for years. The resources are shared between them
Between what? Resource files from the same sourceSet? Please explain me
and one overrides the other
I don't understand anything hahaha
You haven't read the documentation well.
If you list multiple directories, Gradle uses all of them to collect
sources. Because Gradle gives these directories equal priority, if
you define the same resource in more than one directory, you get an
error when merging resources. The default directory is 'src/main/res'.
You haven't understood the only thing you have read:
By default, Android Studio creates the main/ source set and directories for everything you want to share between all your build variants.
That means:
src/
main/ <-- source set
java/ <-- directory
res/ <-- directory
This is what you find when you create a new Android project.
And I told you the default (for resources) is:
sourceSets {
main.res.srcDirs = ['src/main/res']
}
On the other hand:
However, you can create new source sets to control exactly what files Gradle compiles and packages for specific build types, product flavors (and combinations of product flavors when using flavor dimensions), and build variants.
And a few lines below, the sample:
android {
...
sourceSets {
// Encapsulates configurations for the main source set.
main {
// Changes the directory for Java sources. The default directory is
// 'src/main/java'.
java.srcDirs = ['other/java']
// If you list multiple directories, Gradle uses all of them to collect
// sources. Because Gradle gives these directories equal priority, if
// you define the same resource in more than one directory, you get an
// error when merging resources. The default directory is 'src/main/res'.
res.srcDirs = ['other/res1', 'other/res2']
// Note: You should avoid specifying a directory which is a parent to one
// or more other directories you specify. For example, avoid the following:
// res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
// You should specify either only the root 'other/res1' directory, or only the
// nested 'other/res1/layouts' and 'other/res1/strings' directories.
// For each source set, you can specify only one Android manifest.
// By default, Android Studio creates a manifest for your main source
// set in the src/main/ directory.
manifest.srcFile 'other/AndroidManifest.xml'
...
}
// Create additional blocks to configure other source sets.
androidTest {
// If all the files for a source set are located under a single root
// directory, you can specify that directory using the setRoot property.
// When gathering sources for the source set, Gradle looks only in locations
// relative to the root directory you specify. For example, after applying the
// configuration below for the androidTest source set, Gradle looks for Java
// sources only in the src/tests/java/ directory.
setRoot 'src/tests'
...
}
}
}
...
And I suggested you add something like this:
sourceSets {
productionFlavor {
main.res.srcDirs = ['src/main/res', 'src/production/res']
}
developmentFlavor {
main.res.srcDirs = ['src/main/res', 'src/dev/res']
}
}
And finally you say:
There are no issues of "Duplicate resources" , not when we used v0.9 of StringCare, and not when we didn't use it at all.
StringCare doesn't have any relation with "Duplicate resources" error. If you read fine the documentation, you'll see this:
Because Gradle gives these directories equal priority, if
you define the same resource in more than one directory, you get an
error when merging resources. The default directory is 'src/main/res'
To make sure I'm correct, I've tested what I just have explained to you. I have used the KotlinSample
project.
Removing sourceSets
from KotlinSample
project you'll get this:
The strings from different sourceSets aren't detected until I add:
sourceSets {
main.res.srcDirs = ['src/main/res', 'src/other_source/res']
}
I tried to create an empty project (without StringCare) and define two simple strings.xml
files with the same resource identifier.
<string name="app_name">TestApp</string>
<string name="app_name">TestApp zsdfvgbzsadfvb</string>
The additional string file isn't detected until I add this:
sourceSets {
main.res.srcDirs = ['src/main/res', 'src/other_source/res']
}
And after that, I got the Duplicate resources
error:
You can check it in the following project or in the KotlinSample project as was explained in the last comment:
TestApp.zip
I don't see in your sample project that you can choose between 2 build variants of apps :
and removing the part of sourceSets
didn't affect anything. Still works fine.
You also didn't set productFlavors
. Also just one package name, which doesn't make sense if you want to make 2 different apps.
This can't be an example of 2 different apps sharing code and/or resources.
Here, I've found some samples for you to try:
https://github.com/obaro/MultipleFlavorSample.git (based on https://www.androidauthority.com/building-multiple-flavors-android-app-706436/ )
https://github.com/bmuschko/gradle-android-examples/tree/master/product-flavors
https://github.com/bkraszewski/MultiFlavorDemo (based on https://softwarehut.com/multi-flavor-apps-development-android-need/ )
Notice how they all of them don't need to set sourceSets
. The articles don't even mention it, as it's very optional.
And it was this way for years. So much that in order to import those projects and run them, you need to update them a lot .
Here, I've made the smallest sample I can think of, to show how it works :
MyApplication.zip
Also watch the video, showing that indeed it works fine, using the same string, on both apps, yet with different value:
2019-06-25_10-53-36.zip
Notice that it works fine, without errors of duplicate strings. After all, that's the whole point of variants. To be able to override the resources (and even code) , to provide an alternative way to view and use the app (example: lite vs pro) .
Why close this issue?
Maybe you want to create a new one?
I closed the issue because it’s solved.
On the other side you try to understand how sourceSets works and it doesn’t have any relation with StringCare.
My last comment proves it.
I didn't ask about sourceSets . I don't even use it. I said you don't need it for app flavors.
If it's not related to StringCare, please explain why it causes issues when trying to use StringCare, while building&rnning a different flavor.
I don't know what exactly "proves it", and what is "it" that was proven. The last comment doesn't have a sample code of flavors at all, so I showed a sample that does, and it works fine without any sourceSet.
Even the sample that you've provided works fine without any sourceSet, so I don't understand what is this sample for.
There is a bug in StringCare, which causes the app not being able to be built. It might be related to translations, and it might be related to flavors, and maybe to both.
In short, the bug still exists.
It proves that (without StringCare) wrong configurations causes build errors.
Even the sample that you've provided works fine without any sourceSet, so I don't understand what is this sample for.
You don't (want to) understand anything hahaha
If you run TestApp.zip this is the first you get running the app, but you don't say it ;)
Duplicate resources
Say whatever you want, but it fails without using StringCare.
You can do a second test removing the sourceSets and trying to call a string resource on the other source from the main source. You'll get something like this:
I'm not gonna answer any more. This is a waste of time.