nimbus-gradle-plugin: Overhaul the plugin to support lazy configuration.

This is a substantial refactor of the plugin that should fix issues
like bug 1854254 and bug 1856461, and speed up FML generation. These
bugs had the same underlying cause: the plugin was trying to configure
its tasks without observing the Gradle build lifecycle, leading to
inconsistencies and accidental interdependencies.

Further, because the old tasks didn't declare their inputs or outputs,
Gradle couldn't "see" the full task graph, causing too much or too
little to be rebuilt. The easiest way to force Gradle to pick up the
changes used to be a full clobber.

This commit resolves all those issues by:

* Adopting Gradle's lazy configuration API [1], which lets
  Gradle track inputs and outputs, and avoids ordering issues
  caused by realizing the task graph at the wrong time [2].
  As a bonus, we should be able to work much better with
  `afterEvaluate` [3] in m-c's Gradle scripts.
* Adopting the new Android Gradle Plugin `SourceDirectories`
  API that supports lazy configuration [4], which
  replaces the deprecated `registerJavaGeneratingTask` API.
* Adding task classes and custom types to help with naming
  and configuring inputs and outputs.

[1]: https://docs.gradle.org/current/userguide/lazy_configuration.html
[2]: https://docs.gradle.org/current/userguide/task_configuration_avoidance.html
[3]: https://mbonnin.hashnode.dev/my-life-after-afterevaluate
[4]: https://developer.android.com/reference/tools/gradle-api/8.4/com/android/build/api/variant/SourceDirectories
This commit is contained in:
Lina Butler 2024-05-07 08:31:54 -07:00
Родитель 574be82135
Коммит 9197c0bdce
5 изменённых файлов: 510 добавлений и 276 удалений

Просмотреть файл

@ -0,0 +1,209 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.appservices.tooling.nimbus
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.FileVisitDetails
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.LocalState
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import javax.inject.Inject
import groovy.transform.Immutable
/**
* A task that fetches a prebuilt `nimbus-fml` binary for the current platform.
*
* Prebuilt binaries for all platforms are packaged into ZIP archives, and
* published to sources like `archive.mozilla.org` (for releases) or
* TaskCluster (for nightly builds).
*
* This task takes a variable number of inputs: a list of archive sources,
* and a list of glob patterns to find the binary for the current platform
* in the archive.
*
* The unzipped binary is this task's only output. This output is then used as
* an optional input to the `NimbusFmlCommandTask`s.
*/
@CacheableTask
abstract class NimbusAssembleToolsTask extends DefaultTask {
@Inject
abstract ArchiveOperations getArchiveOperations()
@Nested
abstract FetchSpec getFetchSpec()
@Nested
abstract UnzipSpec getUnzipSpec()
/** The location of the fetched ZIP archive. */
@LocalState
abstract RegularFileProperty getArchiveFile()
/**
* The location of the fetched hash file, which contains the
* archive's checksum.
*/
@LocalState
abstract RegularFileProperty getHashFile()
/** The location of the unzipped binary. */
@OutputFile
abstract RegularFileProperty getFmlBinary()
/**
* Configures the task to download the archive.
*
* @param action The configuration action.
*/
void fetch(Action<FetchSpec> action) {
action.execute(fetchSpec)
}
/**
* Configures the task to extract the binary from the archive.
*
* @param action The configuration action.
*/
void unzip(Action<UnzipSpec> action) {
action.execute(unzipSpec)
}
@TaskAction
void assembleTools() {
def sources = [fetchSpec, *fetchSpec.fallbackSources.get()].collect {
new Source(new URI(it.archive.get()), new URI(it.hash.get()))
}
def successfulSource = sources.find { it.trySaveArchiveTo(archiveFile.get().asFile) }
if (successfulSource == null) {
throw new GradleException("Couldn't fetch archive from any of: ${sources*.archiveURI.collect { "`$it`" }.join(', ')}")
}
// We get the checksum, although don't do anything with it yet;
// Checking it here would be able to detect if the zip file was tampered with
// in transit between here and the server.
// It won't detect compromise of the CI server.
try {
successfulSource.saveHashTo(hashFile.get().asFile)
} catch (IOException e) {
throw new GradleException("Couldn't fetch hash from `${successfulSource.hashURI}`", e)
}
def zipTree = archiveOperations.zipTree(archiveFile.get())
def visitedFilePaths = []
zipTree.matching {
include unzipSpec.includePatterns.get()
}.visit { FileVisitDetails details ->
if (!details.directory) {
if (visitedFilePaths.empty) {
details.copyTo(fmlBinary.get().asFile)
fmlBinary.get().asFile.setExecutable(true)
}
visitedFilePaths.add(details.relativePath)
}
}
if (visitedFilePaths.size() > 1) {
throw new GradleException("Ambiguous unzip spec matched ${visitedFilePaths.size()} files in archive: ${visitedFilePaths.collect { "`$it`" }.join(', ')}")
}
}
/**
* Specifies the source from which to fetch the archive and
* its hash file.
*/
static abstract class FetchSpec extends SourceSpec {
@Inject
abstract ObjectFactory getObjectFactory()
@Nested
abstract ListProperty<SourceSpec> getFallbackSources()
/**
* Configures a fallback to try if the archive can't be fetched
* from this source.
*
* The task will try fallbacks in the order in which they're
* configured.
*
* @param action The configuration action.
*/
void fallback(Action<SourceSpec> action) {
def spec = objectFactory.newInstance(SourceSpec)
action(spec)
fallbackSources.add(spec)
}
}
/** Specifies the URL of an archive and its hash file. */
static abstract class SourceSpec {
@Input
abstract Property<String> getArchive()
@Input
abstract Property<String> getHash()
}
/**
* Specifies which binary to extract from the fetched archive.
*
* The spec should only match one file in the archive. If the spec
* matches multiple files in the archive, the task will fail.
*/
static abstract class UnzipSpec {
@Input
abstract ListProperty<String> getIncludePatterns()
/**
* Includes all files whose paths match the pattern.
*
* @param pattern An Ant-style glob pattern.
* @see org.gradle.api.tasks.util.PatternFilterable#include
*/
void include(String pattern) {
includePatterns.add(pattern)
}
}
/** A helper to fetch an archive and its hash file. */
@Immutable
static class Source {
URI archiveURI
URI hashURI
boolean trySaveArchiveTo(File destination) {
try {
saveURITo(archiveURI, destination)
true
} catch (IOException ignored) {
false
}
}
void saveHashTo(File destination) {
saveURITo(hashURI, destination)
}
private static void saveURITo(URI source, File destination) {
source.toURL().withInputStream { from ->
destination.withOutputStream { out ->
out << from
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.appservices.tooling.nimbus
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.LocalState
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.process.ExecSpec
@CacheableTask
abstract class NimbusFeaturesTask extends NimbusFmlCommandTask {
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getInputFile()
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
abstract ConfigurableFileCollection getRepoFiles()
@Input
abstract Property<String> getChannel()
@LocalState
abstract DirectoryProperty getCacheDir()
@OutputDirectory
abstract DirectoryProperty getOutputDir()
@Override
void configureFmlCommand(ExecSpec spec) {
spec.with {
args 'generate'
args '--language', 'kotlin'
args '--channel', channel.get()
args '--cache-dir', cacheDir.get()
for (File file : repoFiles) {
args '--repo-file', file
}
args inputFile.get().asFile
args outputDir.get().asFile
}
}
}

Просмотреть файл

@ -0,0 +1,93 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.appservices.tooling.nimbus
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
import org.gradle.process.ExecSpec
import javax.inject.Inject
/**
* A base task to execute a `nimbus-fml` command.
*
* Subclasses can declare additional inputs and outputs, and override
* `configureFmlCommand` to set additional command arguments.
*
* This task requires either `applicationServicesDir` to be set, or
* the `fmlBinary` to exist. If `applicationServicesDir` is set,
* the task will run `nimbus-fml` from the Application Services repo;
* otherwise, it'll fall back to a prebuilt `fmlBinary`.
*/
abstract class NimbusFmlCommandTask extends DefaultTask {
public static final String APPSERVICES_FML_HOME = 'components/support/nimbus-fml'
@Inject
abstract ExecOperations getExecOperations()
@Inject
abstract ProjectLayout getProjectLayout()
@Input
abstract Property<String> getProjectDir()
@Input
@Optional
abstract Property<String> getApplicationServicesDir()
// `@InputFiles` instead of `@InputFile` because we don't want
// the task to fail if the `fmlBinary` file doesn't exist
// (https://github.com/gradle/gradle/issues/2016).
@InputFiles
@PathSensitive(PathSensitivity.NONE)
abstract RegularFileProperty getFmlBinary()
/**
* Configures the `nimbus-fml` command for this task.
*
* This method is invoked from the `@TaskAction` during the execution phase,
* and so has access to the final values of the inputs and outputs.
*
* @param spec The specification for the `nimbus-fml` command.
*/
abstract void configureFmlCommand(ExecSpec spec)
@TaskAction
void execute() {
execOperations.exec { spec ->
spec.with {
// Absolutize `projectDir`, so that we can resolve our paths
// against it. If it's already absolute, it'll be used as-is.
def projectDir = projectLayout.projectDirectory.dir(projectDir.get())
def localAppServices = applicationServicesDir.getOrNull()
if (localAppServices == null) {
if (!fmlBinary.get().asFile.exists()) {
throw new GradleException("`nimbus-fml` wasn't downloaded and `nimbus.applicationServicesDir` isn't set")
}
workingDir projectDir
commandLine fmlBinary.get().asFile
} else {
def cargoManifest = projectDir.file("$localAppServices/$APPSERVICES_FML_HOME/Cargo.toml").asFile
commandLine 'cargo'
args 'run'
args '--manifest-path', cargoManifest
args '--'
}
}
configureFmlCommand(spec)
}
}
}

Просмотреть файл

@ -4,17 +4,13 @@
package org.mozilla.appservices.tooling.nimbus
import org.gradle.api.Task
import org.gradle.api.provider.ListProperty
import java.util.stream.Collectors
import java.util.zip.ZipFile
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Exec
import org.gradle.api.provider.Provider
abstract class NimbusPluginExtension {
/**
@ -27,11 +23,6 @@ abstract class NimbusPluginExtension {
*/
abstract Property<String> getManifestFile()
File getManifestFileActual(Project project) {
var filename = this.manifestFile.getOrNull() ?: "nimbus.fml.yaml"
return project.file(filename)
}
/**
* The mapping between the build variant and the release channel.
*
@ -40,11 +31,6 @@ abstract class NimbusPluginExtension {
*/
abstract MapProperty<String, String> getChannels()
String getChannelActual(variant) {
Map<String, String> channels = this.channels.get() ?: new HashMap()
return channels.getOrDefault(variant.name, variant.name)
}
/**
* The filename of the manifest ingested by Experimenter.
*
@ -55,11 +41,6 @@ abstract class NimbusPluginExtension {
*/
abstract Property<String> getExperimenterManifest()
File getExperimenterManifestActual(Project project) {
var filename = this.experimenterManifest.getOrNull() ?: ".experimenter.json"
return project.file(filename)
}
/**
* The directory to which the generated files should be written.
*
@ -69,11 +50,6 @@ abstract class NimbusPluginExtension {
*/
abstract Property<String> getOutputDir()
File getOutputDirActual(Object variant, Project project) {
var outputDir = this.outputDir.getOrNull() ?: "generated/source/nimbus/${variant.name}/kotlin"
return project.layout.buildDirectory.dir(outputDir).get().asFile
}
/**
* The file(s) containing the version(s)/ref(s)/location(s) for additional repositories.
*
@ -83,13 +59,6 @@ abstract class NimbusPluginExtension {
*/
abstract ListProperty<String> getRepoFiles()
List<File> getRepoFilesActual(Project project) {
var repoFiles = this.repoFiles.getOrNull() ?: new ArrayList<File>()
return repoFiles.stream().map(filename -> {
project.file(filename)
}).collect(Collectors.toList())
}
/**
* The directory where downloaded files are or where they should be cached.
*
@ -99,11 +68,6 @@ abstract class NimbusPluginExtension {
*/
abstract Property<String> getCacheDir()
File getCacheDirActual(Project project) {
var cacheDir = this.cacheDir.getOrNull() ?: "nimbus-cache"
return project.rootProject.layout.buildDirectory.dir(cacheDir).get().asFile
}
/**
* The directory where a local installation of application services can be found.
*
@ -113,127 +77,96 @@ abstract class NimbusPluginExtension {
* @return
*/
abstract Property<String> getApplicationServicesDir()
File getApplicationServicesDirActual(Project project) {
var applicationServicesDir = this.applicationServicesDir.getOrNull()
return applicationServicesDir ? project.file(applicationServicesDir) : null
}
}
class NimbusPlugin implements Plugin<Project> {
public static final String APPSERVICES_FML_HOME = "components/support/nimbus-fml"
void apply(Project project) {
def extension = project.extensions.create('nimbus', NimbusPluginExtension)
Collection<Task> oneTimeTasks = new ArrayList<>()
if (project.hasProperty("android")) {
if (project.android.hasProperty('applicationVariants')) {
project.android.applicationVariants.all { variant ->
setupVariantTasks(variant, project, extension, oneTimeTasks, false)
}
}
// Configure default values ("conventions") for our
// extension properties.
extension.manifestFile.convention('nimbus.fml.yaml')
extension.cacheDir.convention('nimbus-cache')
if (project.android.hasProperty('libraryVariants')) {
project.android.libraryVariants.all { variant ->
setupVariantTasks(variant, project, extension, oneTimeTasks, true)
def assembleToolsTask = setupAssembleNimbusTools(project)
def validateTask = setupValidateTask(project)
validateTask.configure {
// Gradle tracks the dependency on the `nimbus-fml` binary that the
// `assembleNimbusTools` task produces implicitly; we don't need an
// explicit `dependsOn` here.
fmlBinary = assembleToolsTask.flatMap { it.fmlBinary }
}
if (project.hasProperty('android')) {
// If the Android Gradle Plugin is configured, add the sources
// generated by the `nimbusFeatures{variant}` task to the sources
// for that variant. `variant.sources` is the modern, lazy
// replacement for the deprecated `registerJavaGeneratingTask` API.
def androidComponents = project.extensions.getByName('androidComponents')
androidComponents.onVariants(androidComponents.selector().all()) { variant ->
def generateTask = setupNimbusFeatureTasks(variant, project)
generateTask.configure {
fmlBinary = assembleToolsTask.flatMap { it.fmlBinary }
dependsOn validateTask
}
variant.sources.java.addGeneratedSourceDirectory(generateTask) { it.outputDir }
}
} else {
setupVariantTasks([
// Otherwise, if we aren't building for Android, add an explicit
// dependency on the `nimbusFeatures` task to each `*compile*`
// task.
def generateTask = setupNimbusFeatureTasks([
name: project.name
],
project, extension, oneTimeTasks, true)
], project)
generateTask.configure {
fmlBinary = assembleToolsTask.flatMap { it.fmlBinary }
dependsOn validateTask
}
project.tasks.named {
it.contains('compile')
}.configureEach { task ->
task.dependsOn generateTask
}
}
}
def setupAssembleNimbusTools(Project project, NimbusPluginExtension extension) {
return project.task("assembleNimbusTools") {
def setupAssembleNimbusTools(Project project) {
return project.tasks.register('assembleNimbusTools', NimbusAssembleToolsTask) { task ->
group "Nimbus"
description "Fetch the Nimbus FML tools from Application Services"
doLast {
if (extension.getApplicationServicesDirActual(project) == null) {
fetchNimbusBinaries(project)
} else {
println("Using local application services")
}
}
}
}
// Try one or more hosts to download the given file.
// Return the hostname that successfully downloaded, or null if none succeeded.
static def tryDownload(File directory, String filename, String[] urlPrefixes) {
return urlPrefixes.find { prefix ->
def urlString = filename == null ? prefix : "$prefix/$filename"
try {
new URL(urlString).withInputStream { from ->
new File(directory, filename).withOutputStream { out ->
out << from;
}
}
true
} catch (e) {
false
}
}
}
def asVersion = getProjectVersion()
def fmlRoot = getFMLRoot(project, asVersion)
// Fetches and extracts the pre-built nimbus-fml binaries
def fetchNimbusBinaries(Project project) {
def asVersion = getProjectVersion()
archiveFile = fmlRoot.map { it.file('nimbus-fml.zip') }
hashFile = fmlRoot.map { it.file('nimbus-fml.sha256') }
fmlBinary = fmlRoot.map { it.file(getFMLFile()) }
def fmlPath = getFMLFile(project, asVersion)
println("Checking fml binaries in $fmlPath")
if (fmlPath.exists()) {
println("nimbus-fml already exists at $fmlPath")
return
}
def rootDirectory = getFMLRoot(project, asVersion)
def archive = new File(rootDirectory, "nimbus-fml.zip")
ensureDirExists(rootDirectory)
if (!archive.exists()) {
println("Downloading nimbus-fml cli version $asVersion")
def successfulHost = tryDownload(archive.getParentFile(), archive.getName(),
fetch {
// Try archive.mozilla.org release first
"https://archive.mozilla.org/pub/app-services/releases/$asVersion",
// Try a github release next (TODO: remove this once we verify that publishing to
// archive.mozilla.org is working).
"https://github.com/mozilla/application-services/releases/download/v$asVersion",
archive = "https://archive.mozilla.org/pub/app-services/releases/$asVersion/nimbus-fml.zip"
hash = "https://archive.mozilla.org/pub/app-services/releases/$asVersion/nimbus-fml.sha256"
// Fall back to a nightly release
"https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.nimbus-fml.$asVersion/artifacts/public/build"
)
if (successfulHost == null) {
throw GradleException("Unable to download nimbus-fml tooling with version $asVersion.\n\nIf you are using a development version of the Nimbus Gradle Plugin, please set `applicationServicesDir` in your `build.gradle`'s nimbus block as the path to your local application services directory relative to your project's root.")
} else {
println("Downloaded nimbus-fml from $successfulHost")
}
// We get the checksum, although don't do anything with it yet;
// Checking it here would be able to detect if the zip file was tampered with
// in transit between here and the server.
// It won't detect compromise of the CI server.
tryDownload(rootDirectory, "nimbus-fml.sha256", successfulHost)
}
def archOs = getArchOs()
println("Unzipping binary, looking for $archOs/nimbus-fml")
def zipFile = new ZipFile(archive)
zipFile.entries().findAll { entry ->
return !entry.directory && entry.name.contains(archOs)
}.each { entry ->
fmlPath.withOutputStream { out ->
zipFile.getInputStream(entry).withStream { from ->
out << from
fallback {
archive = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.nimbus-fml.$asVersion/artifacts/public/build/nimbus-fml.zip"
hash = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.nimbus-fml.$asVersion/artifacts/public/build/nimbus-fml.sha256"
}
}
fmlPath.setExecutable(true)
unzip {
include "${getArchOs()}/release/nimbus-fml*"
}
onlyIf('`applicationServicesDir` == null') {
project.nimbus.applicationServicesDir.getOrNull() == null
}
}
}
@ -244,8 +177,8 @@ class NimbusPlugin implements Plugin<Project> {
* @param version
* @return
*/
static File getFMLRoot(Project project, String version) {
return project.layout.buildDirectory.dir("bin/nimbus/$version").get().asFile
static Provider<Directory> getFMLRoot(Project project, String version) {
return project.layout.buildDirectory.dir("bin/nimbus/$version")
}
static def getArchOs() {
@ -276,17 +209,13 @@ class NimbusPlugin implements Plugin<Project> {
return "${archPart}-${osPart}"
}
static File getFMLFile(Project project, String version) {
static String getFMLFile() {
String os = System.getProperty("os.name").toLowerCase()
String binaryName = "nimbus-fml"
if (os.contains("win")) {
binaryName = "nimbus-fml.exe"
}
return new File(getFMLRoot(project, version), binaryName)
}
static String getFMLPath(Project project, String version) {
return getFMLFile(project, version).getPath()
return binaryName
}
String getProjectVersion() {
@ -296,163 +225,66 @@ class NimbusPlugin implements Plugin<Project> {
return props.get("version")
}
def setupVariantTasks(variant, project, extension, oneTimeTasks, isLibrary = false) {
def task = setupNimbusFeatureTasks(variant, project, extension)
if (oneTimeTasks.isEmpty()) {
// The extension doesn't seem to be ready until now, so we have this complicated
// oneTimeTasks thing going on here. Ideally, we'd run this outside of this function.
def assembleToolsTask = setupAssembleNimbusTools(project, extension)
oneTimeTasks.add(assembleToolsTask)
def validateTask = setupValidateTask(project, extension)
validateTask.dependsOn(assembleToolsTask)
oneTimeTasks.add(validateTask)
}
// Generating experimenter manifest is cheap, for now.
// So we generate this every time.
// In the future, we should try and make this an incremental task.
oneTimeTasks.forEach {oneTimeTask ->
if (oneTimeTask != null) {
task.dependsOn(oneTimeTask)
}
}
}
def setupNimbusFeatureTasks(variant, project, extension) {
String channel = extension.getChannelActual(variant)
File inputFile = extension.getManifestFileActual(project)
File outputDir = extension.getOutputDirActual(variant, project)
File cacheDir = extension.getCacheDirActual(project)
List<File> repoFiles = extension.getRepoFilesActual(project)
var generateTask = project.task("nimbusFeatures${variant.name.capitalize()}", type: Exec) {
def setupNimbusFeatureTasks(Object variant, Project project) {
return project.tasks.register("nimbusFeatures${variant.name.capitalize()}", NimbusFeaturesTask) {
description = "Generate Kotlin data classes for Nimbus enabled features"
group = "Nimbus"
doFirst {
ensureDirExists(outputDir)
ensureDirExists(cacheDir)
println("Nimbus FML generating Kotlin")
println("manifest $inputFile")
println("cache dir $cacheDir")
println("repo file(s) ${repoFiles.join(", ")}")
println("channel $channel")
println("manifest ${inputFile.get().asFile}")
println("cache dir ${cacheDir.get().asFile}")
println("repo file(s) ${repoFiles.files.join()}")
println("channel ${channel.get()}")
}
doLast {
println("outputFile $outputDir")
println("outputFile ${outputDir.get().asFile}")
}
def localAppServices = extension.getApplicationServicesDirActual(project)
if (localAppServices == null) {
workingDir project.rootDir
commandLine getFMLPath(project, getProjectVersion())
} else {
def cargoManifest = new File(localAppServices, "$APPSERVICES_FML_HOME/Cargo.toml")
commandLine "cargo"
args "run"
args "--manifest-path", cargoManifest
args "--"
projectDir = project.rootDir.toString()
repoFiles = project.files(project.nimbus.repoFiles)
applicationServicesDir = project.nimbus.applicationServicesDir
inputFile = project.layout.projectDirectory.file(project.nimbus.manifestFile)
cacheDir = project.layout.buildDirectory.dir(project.nimbus.cacheDir).map {
// The `nimbusFeatures*` and `nimbusValidate` tasks can
// technically use the same cache directory, but Gradle
// discourages this, because such "overlapping outputs"
// inhibit caching and parallelization
// (https://github.com/gradle/gradle/issues/28394).
it.dir("features${variant.name.capitalize()}")
}
args "generate"
args "--language", "kotlin"
args "--channel", channel
args "--cache-dir", cacheDir
for (File file : repoFiles) {
args "--repo-file", file
}
args inputFile
args outputDir
println args
channel = project.nimbus.channels.getting(variant.name).orElse(variant.name)
outputDir = project.layout.buildDirectory.dir("generated/source/nimbus/${variant.name}/kotlin")
}
if(variant.metaClass.respondsTo(variant, 'registerJavaGeneratingTask', Task, File)) {
variant.registerJavaGeneratingTask(generateTask, outputDir)
}
def generateSourcesTask = project.tasks.findByName("generate${variant.name.capitalize()}Sources")
if (generateSourcesTask != null) {
generateSourcesTask.dependsOn(generateTask)
} else {
project.tasks.findAll().stream()
.filter({ task ->
return task.name.contains("compile")
})
.forEach({ task ->
task.dependsOn(generateTask)
})
}
return generateTask
}
def setupValidateTask(project, extension) {
File inputFile = extension.getManifestFileActual(project)
File cacheDir = extension.getCacheDirActual(project)
List<File> repoFiles = extension.getRepoFilesActual(project)
return project.task("nimbusValidate", type: Exec) {
def setupValidateTask(Project project) {
return project.tasks.register('nimbusValidate', NimbusValidateTask) {
description = "Validate the Nimbus feature manifest for the app"
group = "Nimbus"
doFirst {
ensureDirExists(cacheDir)
println("Nimbus FML: validating manifest")
println("manifest $inputFile")
println("cache dir $cacheDir")
println("repo file(s) ${repoFiles.join()}")
println("manifest ${inputFile.get().asFile}")
println("cache dir ${cacheDir.get().asFile}")
println("repo file(s) ${repoFiles.files.join()}")
}
def localAppServices = extension.getApplicationServicesDirActual(project)
if (localAppServices == null) {
workingDir project.rootDir
commandLine getFMLPath(project, getProjectVersion())
} else {
def cargoManifest = new File(localAppServices, "$APPSERVICES_FML_HOME/Cargo.toml")
commandLine "cargo"
args "run"
args "--manifest-path", cargoManifest
args "--"
}
args "validate"
args "--cache-dir", cacheDir
for (File file : repoFiles) {
args "--repo-file", file
projectDir = project.rootDir.toString()
repoFiles = project.files(project.nimbus.repoFiles)
applicationServicesDir = project.nimbus.applicationServicesDir
inputFile = project.layout.projectDirectory.file(project.nimbus.manifestFile)
cacheDir = project.layout.buildDirectory.dir(project.nimbus.cacheDir).map {
it.dir('validate')
}
args inputFile
// `nimbusValidate` doesn't have any outputs, so Gradle will always
// run it, even if its inputs haven't changed. This predicate tells
// Gradle to ignore the outputs, and only consider the inputs, for
// up-to-date checks.
outputs.upToDateWhen { true }
}
}
static def ensureDirExists(File dir) {
if (dir.exists()) {
if (!dir.isDirectory()) {
dir.delete()
dir.mkdirs()
}
} else {
dir.mkdirs()
}
}
static def versionCompare(String versionA, String versionB) {
def a = versionA.split("\\.", 3)
def b = versionB.split("\\.", 3)
for (i in 0..<a.length) {
def na = Integer.parseInt(a[i])
def nb = Integer.parseInt(b[i])
if (na > nb) {
return 1
} else if (na < nb) {
return -1
}
}
return 0
}
}

Просмотреть файл

@ -0,0 +1,44 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.appservices.tooling.nimbus
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.LocalState
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.process.ExecSpec
@CacheableTask
abstract class NimbusValidateTask extends NimbusFmlCommandTask {
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getInputFile()
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
abstract ConfigurableFileCollection getRepoFiles()
@LocalState
abstract DirectoryProperty getCacheDir()
@Override
void configureFmlCommand(ExecSpec spec) {
spec.with {
args 'validate'
args '--cache-dir', cacheDir.get()
for (File file : repoFiles) {
args '--repo-file', file
}
args inputFile.get()
}
}
}