Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate assets issue #226

Open
realdadfish opened this issue Sep 14, 2022 · 9 comments
Open

Duplicate assets issue #226

realdadfish opened this issue Sep 14, 2022 · 9 comments

Comments

@realdadfish
Copy link
Contributor

realdadfish commented Sep 14, 2022

In

the plugin determines all configured assets directories of a specific build variant to be able to copy the generated resources to it.

IMHO this is the wrong approach, because this leads to a "duplicate assets" issue as shown below:

> Task :app:mergeDebugAssets FAILED
ERROR:/path/to/app/src/main/assets/open_source_licenses.html /path/to/app/build/generated/other-generated-assets/open_source_licenses.html: Resource and asset merger: Duplicate resources

It doesn't make much sense to just take "the first" or only the first not in /build either, because you cannot foresee what the module owner had in mind when (s)he configured his module with multiple asset directories. A better approach would be

  1. Add your own build directory to the assets source set, so it can be merged with the rest of the assets, or
  2. Allow users to configure a specific asset directory where generated assets should be copied to

Obviously, these two options should be mutual exclusive, otherwise people will stumble upon the same error above.

@realdadfish
Copy link
Contributor Author

On a related note - generated assets should be separated by build variant under Android, so that

build/reports/licenses/firstVariant
build/reports/licenses/secondVariant
...

are all separated from each other and each target file can keep its original file name.

That way it is easier in multi-variant environments, because consumers don't have to rename files by hand or via a separate Gradle Copy task and also don't have to distinguish different filenames on runtime.

All you need to do is to add the appropriate variant-named build dir to the assets source set of the same variant and you're done.

@realdadfish
Copy link
Contributor Author

The common output directory is even more problematic in Gradle 8, which introduced more strict task validation rules. Since all of the LicenseReport tasks output to the same configured @OutputDirectory, Gradle 8 cannot properly wire tasks that depend on the output of one specific task to another, further processing task. Consider the following example:

androidComponents.onVariants { variant ->
    val variantName = variant.name.capitalize()
    val licenseReportTask = tasks.matching { it.name == "license${variantName}Report" }
    val copyTask = tasks.register("copy${variantName}LicenseReport", Copy::class.java) {
        from(layout.buildDirectory.dir("reports/licenses/")) {  // <<-- this is the problematic line
            include("license${variantName}Report.*")
            rename("license${variantName}Report.(.*)", "open_source_licenses.$1")
        }
        into(layout.projectDirectory.dir("src/${variant.name}/assets/"))
    }
    licenseReportTask.configureEach {
        finalizedBy(copyTask)
    }
    tasks.matching { it.name == "merge${variantName}Assets" }.all {
        dependsOn(licenseReportTask)
        dependsOn(copyTask)
    }
}

errors out with

Reason: Task ':app:copyDevelopReleaseLicenseReport' uses this output of task ':app:licenseDevelopDebugReport' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed. 

when the tasks for more than one build variant are executed at the same time.

@AndreyBulgakow
Copy link

AndreyBulgakow commented Nov 10, 2023

Are there any news on this? Because I am struggling with it in our gradle upgrade from 7 to 8, in which the plugin produces a lot of error output and does not work properly.

@jaredsburrows
Copy link
Owner

jaredsburrows commented Jan 29, 2024

@realdadfish Are you still having this issue?

I am trying to repro with 0.9.4:

git clone https://github.com/jaredsburrows/android-gif-search
cd android-gif-search
gradlew licenseDebugReport
gradlew licenseReleaseReport
gradlew assembleDebug

@realdadfish
Copy link
Contributor Author

I switched projects in the meantime, in the current one I'm not using the plugin anymore, so I can't say. Maybe @AndreyBulgakow can say?

@sergeys-opera
Copy link
Contributor

At least #339 seems to be fixed in 0.9.4

@sergeys-opera
Copy link
Contributor

Actually the build failed to me on CI:

FAILURE: Build failed with an exception.
* What went wrong:
A problem was found with the configuration of task ':ui:licenseReleaseReport' (type 'LicenseReportTask').
  - Gradle detected a problem with the following location: '/builds/app/ui/build/reports/licenses'.
    
    Reason: Task ':ui:generateDebugLicenseReport' uses this output of task ':ui:licenseReleaseReport' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':ui:licenseReleaseReport' as an input of ':ui:generateDebugLicenseReport'.
      2. Declare an explicit dependency on ':ui:licenseReleaseReport' from ':ui:generateDebugLicenseReport' using Task#dependsOn.
      3. Declare an explicit dependency on ':ui:licenseReleaseReport' from ':ui:generateDebugLicenseReport' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.5/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org/.
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
BUILD FAILED

@jaredsburrows
Copy link
Owner

Where is :ui:generateDebugLicenseReport coming from? There is only one task in this project: licenseReport.

@sergeys-opera
Copy link
Contributor

It's a custom task that triggers a licenseReport task and transforms its output to a different format:

private fun Project.createLicenseTask(
    variantName: String,
    assetDirs: Set<File>,
    ignoredPatterns: Set<String>
) = task("generate${variantName}LicenseReport") {
    // Trigger the license plugin task first to generate the JSON report.
    dependsOn("license${variantName}Report")

    // Prepare output directions and mark them as outputs.
    val outputDirName = "licenses"
    val outputDirs = assetDirs.map {
        it.toPath().resolve(outputDirName).toFile()
    }
    outputDirs.forEach {
        outputs.dir(it)
    }

    // Prepare inputs.
    val jsonReport = licenseReportFile(variantName)
    inputs.file(jsonReport)

    val knownLicensesDir = knownLicencesDir()
    inputs.dir(knownLicensesDir)

    val extraDeps = extraDepsFile()
    inputs.file(extraDeps)

    // Mapping between a license URL and its text.
    val knownLicensesByUrl = knownLicencesByUrl()

    doLast {
        val deps = loadDeps(
            from = jsonReport,
            ignoredPatterns = ignoredPatterns
        ).toMutableMap()

        // Load extra dependencies. These are dependencies coming from other
        // sources then the license report plugin. E.g. if we use a native
        // library or a jar-file requiring attribution.
        deps.putAll(loadDeps(from = extraDeps))

        // Make sure each dependency has a license defined.
        checkLicenseExistence(deps)

        // Add the license file name to the report or fail if missing.
        deps.values.forEach { dep ->
            dep.licenses?.forEach {
                val licenseFile = knownLicensesByUrl[it.licenseUrl]
                if (licenseFile != null) {
                    it.licenseFile = "$outputDirName/$licenseFile"
                } else {
                    throw GradleException("Missing license file for license url: ${it.licenseUrl} coming from ${dep.dependency}")
                }
            }
        }

        // Copy result json report and licenses to assets
        outputDirs.forEach { outputDir ->
            writeOutput(outputDir, deps, knownLicensesDir)
        }
    }
}

While generate${variantName}LicenseReport depends on license${variantName}Report Gradle doesn't like that license${variantName}Report writes reports into the same output folder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants