Gradle: How to init SourceTask's property ‘source’ with extension property? - gradle

My plugin registers an extension and some custom task that inherited from org.gradle.api.tasks.SourceTask.
class MyPlugin implements Plugin<Project> {
private final Instantiator instantiator
private final FileResolver fileResolver
#Inject
MyPlugin (Instantiator instantiator, FileResolver fileResolver) {
this.instantiator = instantiator
this.fileResolver = fileResolver
}
void apply(Project project) {
MyPluginExtension extension = project.extensions.create("myPlugin", MyPluginExtension, project, instantiator, fileResolver)
project.tasks.create('doSomething', MyCustomTask) {}
}
}
class MyPluginExtension {
final MySourceSetContainer source
MyPluginExtension(Project project, Instantiator instantiator, FileResolver fileResolver) {
source = instantiator.newInstance(ImplMySourceSetContainer, project, instantiator, fileResolver)
}
void source(Closure closure) {
ConfigureUtil.configure(closure, source)
}
}
class MyCustomTask extends SourceTask {
#TaskAction
void act() {
// something
}
}
And now, if I configure build script:
myPlugin {
source{
main {
something {
srcDirs "src/main/resources"
}
}
}
}
doSomething {
source = myPlugin.source.main.something.asFileTree
}
- All works fine. But I want to initialize task property source by value from MyPluginExtension.
source = extension.source.findAll().inject(project.files().asFileTree, { result, item -> result + item.html.asFileTree })
I can't extract extension property at the execution phase as it described in the userguide (https://docs.gradle.org/4.2.1/userguide/custom_plugins.html#sec:mapping_extension_properties_to_task_properties), because getter for source that declared in superclass org.gradle.api.tasks.SourceTask marked with annotation #org.gradle.api.tasks.SkipWhenEmpty and task will be skipped.
How can I initialize task's property with value from extension before execution phase?
Thx.

I'm not sure that I fully understand what you are doing but you could likely use a closure to delay evaluation. See Project.files(Object...)
Eg:
doSomething {
def myClosure = {
extension.source.findAll().inject(project.files().asFileTree, { result, item -> result + item.html.asFileTree })
}
source = files(myClosure)
}

Resolved by wrapping source init in closure:
class MyPlugin implements Plugin<Project> {
// ...
void apply(Project project) {
MyPluginExtension extension = project.extensions.create("myPlugin", MyPluginExtension, project, instantiator, fileResolver)
project.tasks.create('doSomething', MyCustomTask) { task ->
task.source = {
extension.source.findAll().inject(new HashSet<File>(), { result, item -> result + item.html.srcDirs })
}
}
}
}

Related

How to generate OpenAPI via Gradle for Amazon Selling Partner API models?

I'm new to Gradle (using 7.3.2) and currently trying to integrate org.openapi.generator's openApiGenerate task for all json OpenAPI template files of Amazon's selling partner API models from https://github.com/amzn/selling-partner-api-models.
plugins {
id 'org.ajoberstar.grgit' version '4.1.1'
id 'org.openapi.generator' version '5.3.0'
}
import org.ajoberstar.grgit.Grgit
project.ext.repoDirectory = "$buildDir/selling-partner-api-models"
project.ext.basePackage = "com.amazon.sellingpartner"
tasks.register('deleteRepo', Delete) {
delete project.ext.repoDirectory
}
class CloneSpapi extends DefaultTask {
#Input
String url = 'https://github.com/amzn/selling-partner-api-models.git'
#TaskAction
def clone() {
println 'Cloning Amazon Selling Partner OpenAPI from github ...'
def grgit = Grgit.clone(dir: project.ext.repoDirectory, uri: url)
grgit.close()
println 'done'
}
}
tasks.register('cloneSpapi', CloneSpapi) {
description 'Clones Amazon Selling Partner OpenAPI from github'
dependsOn tasks.named('deleteRepo')
}
interface GenerateParameters extends WorkParameters {
RegularFileProperty getJsonFile()
DirectoryProperty getOutputDir()
}
abstract class GenerateApi implements WorkAction<GenerateParameters> {
#Override
void execute() {
def file = parameters.jsonFile.get().getAsFile()
def modelName = file.getName().replace('.json', '')
println modelName + ": " + file
}
}
abstract class GenerateApis extends DefaultTask {
private final WorkerExecutor workerExecutor
#Input
abstract String directory = "$project.ext.repoDirectory/models"
#Inject
GenerateApis(WorkerExecutor workerExecutor) {
this.workerExecutor = workerExecutor
}
#TaskAction
void generateFiles() {
ConfigurableFileTree tree = project.fileTree(dir: directory)
tree.include '**/*.json'
Set<File> modelFiles = tree.getFiles().sort()
WorkQueue workQueue = workerExecutor.noIsolation()
modelFiles.each { File file ->
workQueue.submit(GenerateApi.class) { GenerateParameters parameters ->
parameters.jsonFile = file
}
}
}
}
tasks.register('generateApis', GenerateApis) {
}
openApiGenerate {
generatorName = "java"
inputSpec = // file
outputDir = "$buildDir/generated".toString()
invokerPackage = "$project.ext.basePackage"
apiPackage = "$project.ext.basePackage" + ".api.modelName" // modelName from
modelPackage = "$project.ext.basePackage" + ".model.modelName" // modelName here
configOptions = [
dateLibrary: "java8"
]
}
To simplify things, I have added a simple 'cloneSpapi' task to clone the repository.
gradle -q cloneSpapi
How do I call the openApiGenerate task for each template file?
I have now used the swagger.io library directly and was able to call the code generator directly.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("io.swagger:swagger-codegen:2.4.24")
}
}
plugins {
id 'org.ajoberstar.grgit' version '4.1.1'
}
import io.swagger.codegen.DefaultGenerator
import io.swagger.codegen.config.CodegenConfigurator
import org.ajoberstar.grgit.Grgit
project.ext.repoDirectory = "$buildDir/selling-partner-api-models"
project.ext.basePackage = "com.amazon.sellingpartner"
project.ext.outputDir = new File(project.buildDir, '/generated')
tasks.register('deleteRepo', Delete) {
delete project.ext.repoDirectory
}
class CloneSpapi extends DefaultTask {
#Input
String url = 'https://github.com/amzn/selling-partner-api-models.git'
#TaskAction
def clone() {
println 'Cloning Amazon Selling Partner OpenAPI from github ...'
def grgit = Grgit.clone(dir: project.ext.repoDirectory, uri: url)
grgit.close()
println 'done'
}
}
tasks.register('cloneSpapi', CloneSpapi) {
description 'Clones Amazon Selling Partner OpenAPI from github'
dependsOn tasks.named('deleteRepo')
}
interface GenerateParameters extends WorkParameters {
RegularFileProperty getJsonFile()
DirectoryProperty getTemplateDir()
DirectoryProperty getOutputDir()
Property<String> getBasePackage()
}
abstract class GenerateApi implements WorkAction<GenerateParameters> {
#Override
void execute() {
def file = parameters.jsonFile.get().getAsFile()
def modelName = file.getName().replace('.json', '')
println "Generating OpenAPI for $modelName"
def basePackage = parameters.basePackage.get()
def config = new CodegenConfigurator()
config.setInputSpec(file.toString()) // The swagger API file
config.setLang("java")
config.setTemplateDir(parameters.templateDir.get().toString())
config.setOutputDir(parameters.outputDir.get().toString()) // The output directory, user-service-contract/build/user-service-server/
config.setInvokerPackage(basePackage)
config.setApiPackage(basePackage + ".api." + modelName) // Package to be used for the API interfaces
config.setModelPackage(basePackage + ".model." + modelName) // Package to be used for the API models
config.setAdditionalProperties([
'dateLibrary' : 'java8', // Date library to use
'useTags' : 'true' // Use tags for the naming
])
def generator = new DefaultGenerator();
generator.opts(config.toClientOptInput());
generator.generate()
}
}
abstract class GenerateApis extends DefaultTask {
private final WorkerExecutor workerExecutor
#Input
abstract String directory = "$project.ext.repoDirectory/models"
#Inject
GenerateApis(WorkerExecutor workerExecutor) {
this.workerExecutor = workerExecutor
}
#TaskAction
void generate() {
ConfigurableFileTree tree = project.fileTree(dir: directory)
tree.include '**/*.json'
Set<File> modelFiles = tree.getFiles().sort()
WorkQueue workQueue = workerExecutor.noIsolation()
modelFiles.each { File file ->
workQueue.submit(GenerateApi.class) { GenerateParameters parameters ->
parameters.jsonFile = file
parameters.templateDir = new File(project.ext.repoDirectory, 'clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates')
parameters.outputDir = project.ext.outputDir
parameters.basePackage = project.ext.basePackage
}
}
}
}
tasks.register('deleteGenerated', Delete) {
delete project.ext.outputDir
}
tasks.register('generateApis', GenerateApis) {
dependsOn tasks.named('deleteGenerated'), tasks.named('cloneSpapi')
}

Use gradle plugin to configure another gradle plugin

I want to implement a gradle plugin which changes the project.version according to the configuration of the plugin and then use the changed project.version to configure another gradle plugin e.g. for building containers. The problem is now that both configurations are evaluated at the same time and therefore the changes to project.version are not applied at the time the second plugin is configured.
I search the Gradle documentation but found nothing regarding my problem.
build.gradle
class VersionPluginExtension {
String major
String minor
String patch
}
class VersionPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('versionPlugin', VersionPluginExtension)
project.afterEvaluate {
project.version = "${extension.major}.${extension.minor}.${extension.patch}"
}
project.task('showVersion') {
doLast {
println "${project.version}"
}
}
}
}
class ContainerPluginExtension {
String version
}
class ContainerPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('containerPlugin', ContainerPluginExtension)
project.task('build') {
doLast {
println "${extension.version}"
}
}
}
}
apply plugin: VersionPlugin
apply plugin: ContainerPlugin
versionPlugin {
major = '1'
minor = '1'
patch = '1'
}
containerPlugin {
version = project.version
}
I expect that the task build returns 1.1.1 and not unspecified but I think it's not possible in this way. I hope someone can point me in the right direction.
Thanks!
Move the definition of version from the containerPlugin block to the plugin definition :
class ContainerPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('containerPlugin', ContainerPluginExtension)
project.afterEvaluate {
extension.version = project.version
}
project.task('build') {
doLast {
println "${extension.version}"
}
}
}
}
Result :
$ gradle build
> Task :build
1.1.1
The solution to the problem above looks like this:
class VersionPluginExtension {
String major
String minor
String patch
private String version
String getVersion() {
if (!version)
return "${major}.${minor}.${patch}"
return version
}
}
class VersionPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('versionPlugin', VersionPluginExtension)
project.task('showVersion') {
doLast {
println "${extension.version}"
}
}
}
}
class ContainerPluginExtension {
String version
}
class ContainerPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('containerPlugin', ContainerPluginExtension)
project.task('build') {
doLast {
println "${extension.version}"
}
}
}
}
apply plugin: VersionPlugin
apply plugin: ContainerPlugin
versionPlugin {
major = '1'
minor = '1'
patch = '1'
}
project.version = versionPlugin.version
containerPlugin {
version = project.version
}
Result:
> Configure project :
1.1.1
> Task :showVersion
1.1.1
> Task :build
1.1.1

Xamarin.UITest Platform enum always returning Android

I have referred AppInitializer always launches android for cross platform tests post
still none of the solutions are helping me out, whenever I run BDD tests by default Platform is getting value as Android.
How can I make it dynamically pick the platform values?
I'm having a BaseTest class that inherited by all test class with decorated with PlatformTestFixture attribute. Then we can start Android or iOS app based on platform parameter that passed in.
public class PlatformTestFixtureAttribute : TestFixtureAttribute
{
public PlatformTestFixtureAttribute()
{
}
public PlatformTestFixtureAttribute(params object[] arguments)
: base(arguments)
{
AddPlatformCategory(arguments);
}
private void AddPlatformCategory(object[] args)
{
// Not needed in TestCloud
if (!TestEnvironment.IsTestCloud)
{
foreach (var arg in args)
{
if (arg is Platform)
{
if (Platform.Android == (Platform)arg)
{
AddAndroidCategory();
}
else if (Platform.iOS == (Platform)arg)
{
AddiOSCategory();
}
}
}
}
}
private void AddAndroidCategory()
{
Category = "AndroidTest";
}
private void AddiOSCategory()
{
Category = "iOSTest";
}
}
[PlatformTestFixture(Platform.Android)]
[PlatformTestFixture(Platform.iOS)]
public abstract class BaseTest
{
Platform _platform;
protected BaseTest(Platform platform)
{
_platform = platform;
}
[SetUp]
public virtual void BeforeEachTest()
{
if (platform == Platform.Android)
{
StartAndroidApp();
}
else
{
StartiOSApp();
}
}
}
public class ChildTest : BaseTest
{
public ChildTest(Platform platform)
: base(platform)
{
}
[Test]
public void SomeTest()
{
...
}
}
Then I can run the test in command line specifying whether to run iOSTest or AndroidTest.
[NUnit] [Test Fixture DLL Path] -include=AndroidTest
OR
[NUnit] [Test Fixture DLL Path] -include=iOSTest

Using multiple res folders with Robolectric

My current Gradle configuration has multiple (Merged) res folders:
sourceSets {
androidTest {
setRoot('src/test')
}
main {
res.srcDirs =
[
'src/main/res/features/registration',
'src/main/res/features/login',
'src/main/res'
]
}
}
But Robolectric allows me to configure a single directory using AndroidManifest:
public class RobolectricGradleTestRunner extends RobolectricTestRunner {
private static final int MAX_SDK_SUPPORTED_BY_ROBOLECTRIC = 18;
public RobolectricGradleTestRunner(Class<?> testClass) throws InitializationError {
super(testClass);
}
#Override
protected AndroidManifest getAppManifest(Config config) {
String manifestProperty = "../app/src/main/AndroidManifest.xml";
String resProperty = "../app/src/main/res";
return new AndroidManifest(Fs.fileFromPath(manifestProperty), Fs.fileFromPath(resProperty)) {
#Override
public int getTargetSdkVersion() {
return MAX_SDK_SUPPORTED_BY_ROBOLECTRIC;
}
};
}
}
This way tests are failing. Is it possible to configure robolectric to reflect my gradle file?
Another solution similar to Luca's:
public class MyTestRunner extends RobolectricTestRunner {
...
#Override
protected AndroidManifest getAppManifest(Config config) {
String appRoot = "./src/main/";
String manifestPath = appRoot + "AndroidManifest.xml";
String resDir = appRoot + "res";
String assetsDir = appRoot + "assets";
return new AndroidManifest(Fs.fileFromPath(manifestPath), Fs.fileFromPath(resDir), Fs.fileFromPath(assetsDir)) {
#Override
public List<ResourcePath> getIncludedResourcePaths() {
List<ResourcePath> paths = super.getIncludedResourcePaths();
paths.add(new ResourcePath(getRClass(), getPackageName(), Fs.fileFromPath("../app/src/main/res/features/registration"), getAssetsDirectory()));
paths.add(new ResourcePath(getRClass(), getPackageName(), Fs.fileFromPath("../app/src/main/res/features/login"), getAssetsDirectory()));
return paths;
}
};
}
}
Don't forget to annotate your tests with #RunWith(MyTestRunner.class)
Ok, this is the easiest way to do it, You will have to extend RobolectricTestRunner getAppManifest and createAppResourceLoader.
In getAppManifest you will simply have to store the manifest in a field, let's say mDefaultManifest.
In createAppResourceLoader you will have to add the right resources injected.
/**
* TODO: Watch OUT this is copied from RobolectricTestRunner in Robolectric-2.4 keep it up to date!
*/
#Override
protected ResourceLoader createAppResourceLoader(ResourceLoader systemResourceLoader, AndroidManifest appManifest) {
List<PackageResourceLoader> appAndLibraryResourceLoaders = new ArrayList<PackageResourceLoader>();
for (ResourcePath resourcePath : appManifest.getIncludedResourcePaths()) {
appAndLibraryResourceLoaders.add(createResourceLoader(resourcePath));
}
/* BEGIN EDIT */
if(mDefaultManifest != null) {
ResourcePath rpInjected = new ResourcePath(mDefaultManifest.getRClass(), mDefaultManifest.getPackageName(), Fs.fileFromPath("../app/src/main/res/features/registration"), mDefaultManifest.getAssetsDirectory());
appAndLibraryResourceLoaders.add(createResourceLoader(rpInjected));
rpInjected = new ResourcePath(mDefaultManifest.getRClass(), mDefaultManifest.getPackageName(), Fs.fileFromPath("../app/src/main/res/features/login"), mDefaultManifest.getAssetsDirectory());
appAndLibraryResourceLoaders.add(createResourceLoader(rpInjected));
}
/* END EDIT */
OverlayResourceLoader overlayResourceLoader = new OverlayResourceLoader(appManifest.getPackageName(), appAndLibraryResourceLoaders);
Map<String, ResourceLoader> resourceLoaders = new HashMap<String, ResourceLoader>();
resourceLoaders.put("android", systemResourceLoader);
resourceLoaders.put(appManifest.getPackageName(), overlayResourceLoader);
return new RoutingResourceLoader(resourceLoaders);
}
Do not forget to add #RunWith(YourTestRunner.class) in your test classes.

How to access Gradle `project` from `buildSrc`?

I have the following in buildSrc:
class MyClass {
def doSomething() {
final familyMembers = project.configurations['compile'].allDependencies.collect { dep ->
dep.name
}
}
but when I try to use it in build.gradle:
task 'do-something' << {
final myObject = new MyClass()
myObject.doSomething()
}
the following error is emitted:
* What went wrong:
Execution failed for task ':my-project:do-something'.
> No such property: project for class: MyClass
How do I get project to be visible within MyClass?
You'll have to pass project as a parameter to MyClass.
For example, declare a constructor and a member variable:
class MyClass {
private Project project
MyClass(Project project) {
this.project = project
}
def doSomething() {
final familyMembers = project.configurations['compile'].allDependencies.collect { dep ->
dep.name
}
}
and then use it from your project as such:
task 'do-something' << {
final myObject = new MyClass(project)
myObject.doSomething()
}

Resources