I have a custom Gradle plugin, that works well, I have published a couple of versions already.
I have a new requirement, where I want to allow the plugin's users to provide an optional list of dependencies, in which they would provide classes, that can be loaded during the plugin execution.
Looking at the documentation, it seems to be quite close to what is described in https://docs.gradle.org/current/userguide/implementing_gradle_plugins.html#providing_default_dependencies_for_plugins (in my case there's no default though - or said differently, the default should be an empty list of extra dependencies), but I struggle to adapt it to my use case.
right now, my plugin can be configured like this :
archUnit {
preConfiguredRules = ["sample.SampleRule"]
}
the plugin already contains some rules, but I want to allow users to provide their own rules, they should be able to provide something like
dependencies {
archUnitDeps 'org.example:archunit-custom-rules:1.0'
}
if the provided class (sample.SampleRule) is in that extra dependency that we configure, the plugin should load it and apply it, just like it does with the rules that are currently packaged with the plugin.
Following the docs, I create a configuration in my plugin :
Configuration deps = project.getConfigurations().create("archUnitDeps", c -> {
c.setVisible(false);
c.setCanBeConsumed(false);
c.setCanBeResolved(true);
c.setDescription("The packaged extra rules you may want to add ");
});
but then what should I do ?
I've tried something like this, to get the dependencies that the user configures, and add them at project level :
DependencyHandler dependencies = project.getDependencies();
deps.getAllDependencies().stream().forEach( extraDep -> dependencies.create(extraDep));
but when using it in a project, deps.getAllDependencies() is empty, even though I configure the org.example:archunit-custom-rules:1.0 dependency as above. So nothing gets loaded, and I end up with a java.lang.ClassNotFoundException: sample.SampleRule.
what am I missing ? do you have a readily available example of a plugin that does something similar, that I could get inspiration from ?
Finally was able to solve this, thanks to the help I got on https://discuss.gradle.org/t/class-loaded-through-configuration-is-not-found-when-task-runs/42945/3
I am quite surprised there's not a simpler solution, but one way of doing, is to use Gradle worker API, which takes 2 main steps :
in the task, get access to the workQueue, and submit the parameters required for the task execution :
WorkQueue queue = getWorkerExecutor().classLoaderIsolation(spec -> spec.getClasspath().from(getClasspath()));
queue.submit(WorkerAction.class, params -> {
params.getMainClassesPath().set(someValue);
params.getTestClassesPath().set(someOtherValue);
etc..
})
.collect(Collectors.toList()));
});
On the worker side, get back those parameters, and launch what you were initially doing in the task :
public abstract class WorkerAction implements WorkAction<WorkerActionParams> {
#Override
public void execute() {
WorkerActionParams params = getParameters();
....
}
The parameters you pass need to be serializable - I was not able to make that part fully work with my original parameters, so I had to create new "wrapper" ones.
full example is available here : https://github.com/societe-generale/arch-unit-gradle-plugin/pull/24/files
Related
I am trying to do one example with ArchUnit where passing the AnalyzeClasses can be dynamic based on for which Adapter Application the test need run.
For Example:
#AnalyzeClasses(packages = "${archtest.scan.package}", importOptions = { ImportOption.DoNotIncludeTests.class, ImportOption.DoNotIncludeJars.class })
public class ArchitectureTests {
}
And from application.properties file it should allow to pass the packages to analyze dynamically, so any application using this Application as Jar library can provide the scan classes in its properties file. As below.
archtest.scan.package=com.example.pkgname
I am not sure what is the right way to pick up the dynamic value from property and pass that into #AnalyzeClasses Annotation. I am looking for some help or any example in this regard.
I don't think that ArchUnit's JUnit 4 & 5 support – in the current version 0.23.1 – allows for dynamic packages configured via an application.properties.
But instead of using #AnalyzeClasses, you can always just invoke new ClassFileImporter().import… and pass any dynamic runtime values you like.
(Note that ArchUnit's JUnit support also introduces a clever cache to reuse imported JavaClasses by multiple #ArchTests, but storing JavaClasses in a static field may be also good enough.)
This actually should be possible using a custom LocationProvider within #AnalyzeClasses. E.g.
#AnalyzeClasses(locations = ApplicationPropertiesLocationProvider.class)
public class ExampleTest {
// ...
}
class ApplicationPropertiesLocationProvider implements LocationProvider {
#Override
public Set<Location> get(Class<?> testClass) {
String packageToScan = readFromApplicationProperties();
return Locations.ofPackage(packageToScan);
}
}
But be aware of caching limitations! The caching mechanism assumes that your LocationProvider is "idempotent", i.e. it always returns the same locations. The caching mechanism will only take the type of the LocationProvider into consideration as cache key. This should not be a problem for a static application.properties as source though.
I'm trying to add a hook after all the files are created by the Go client generator and I'm wondering where I can add this.
Right now, files are generated in this order (a) models (b) API paths (c) supporting files.
If I hook into AbstractGoCodegen's postProcessSupportingFileData function like so, myfunc() will get called before the supporting files like README.md and client.go are created, but I want the function be called afterwards.
#Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
generateYAMLSpecFile(objs);
objs = super.postProcessSupportingFileData(objs);
myfunc();
return objs;
}
I've also tried a few other other postProcessing* functions as shown in DefaultCodegen but they didn't work as desired.
How can I do this?
If you do not get the desired functionality from overriding the configuration methods, I suggest that you extend the DefaultGenerator class. These contain the actual generation methods such as generateSupportingFiles. You should be able to easily add your hook after this method has generated the supporting files.
Keep in mind that you might have to change a few modifiers from private to protected.
I'm looking at:
https://github.com/typesafehub/config
Let's say I want to have a default configuration, e.g. reference.conf, and then I want to have dev/prod overrides (two different application.conf's), and then I also wanted to have host-specific overrides that inherited from both the application.conf and ultimately the default reference.conf. How would I do this?
e.g., I'm imagining a directory structure something like:
resources/reference.conf
resources/prod/application.conf
resources/prod/master.conf
resources/prod/slave.conf
resources/dev/application.conf
resources/dev/master.conf
resources/dev/slave.conf
Or maybe it would be resources/dev/master/application.conf?
Somewhere I would specify an environment, i.e. maybe extracted from the hostname the application was started on.
If the application was master.dev.example.com, I'm expecting I should be able to do something like:
getConfigurations("dev/master.conf").withDefaultsFrom(
getConfigurations("dev/application.conf").withDefaultsFrom(
getConfigurations("resource.conf"))
But I'm having a hard time understanding what exactly that would look like using the given library.
I see I could set a config.resource system property, but it looks like that would only allow for one level of overrides, dev-application.conf -> resources.conf, not something like master-node.conf -> dev-application.conf -> resources.conf.
I see a .withFallback method, but that seems to be if I wanted to mix two kinds of configuration in a single file, not to chain resources/files together.
Use multiple withFallback with the configs that have the highest priority first. For example:
Config finalConfig =
ConfigFactory.systemProperties().
withFallback(masterConfig).
withFallback(applicationConfig).
withFallback(referenceConfig)
Each of the configs like masterConfig would have been loaded with Config.parseFile. You can also use ConfigFactor.load as a convenience, but the parseXXX methods give you more control over your hierarchy.
I'm having a very difficult time using the Reflections API to find classes that are annotated with a custom annotation at runtime. The ultimate goal is to find all classes in the project that are annotated with my custom #Job annotation, collect them, and allow each of them to be run from one location without adding each one to the page manually. However, I'm finding it extremely difficult to get the initial search to work correctly, so I cannot move on with my project.
My current approach is to use:
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forPackage("jobs"))
.setScanners(new TypeAnnotationsScanner())
.filterInputsBy(new FilterBuilder().includePackage("jobs")));
Set<Class<?>> jobs = reflections.getTypesAnnotatedWith(Job.class);
where "jobs" is the package containing all of the job classes that I am searching for, which will be annotated with the custom #Job annotation. "jobs" is a base package in my project, but the overall url on my machine looks something like ".../(project)/app/jobs". This setup results in one url being searched, which is ".../(project)/app/" with the additional filter "+jobs.*" in the configuration object. This seems like it is working correctly, but clearly something is wrong because I do not get any classes in the set.
If it matters, the annotation is coded as:
package jobs;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Job {
String description();
}
The annotation class is located within the same "jobs" package as the job classes I am searching for. An example of a job definition with the annotation included is:
package jobs;
#Job(description = "Description of what the job will do")
public class ExampleJob extends MasterJob {...}
I cannot find what I need to change in order to get this search to function as intended. Thanks for the help, and please let me know if I can clarify anything further.
EDIT: I believe the problem is associated with how the Play Framework loads its classes. Fortunately, the framework provides its own annotation search function, which I used instead. According to a comment, the code I have listed here will work, given that you have all the dependencies to run it. Feel free to use it as a template and let me know if it works for you as well.
I am using cucumber-java in groovy code. I prefer cucumber-java to cucumber-groovy because I can run the tests like plain old good JUnit tests. However, the step definition templates spitted out by cucumber are in java style. Instead, I would like to have a groovy style. For example, in java style, you will get something like
#When("^an HTTP GET request is sent to obtain config.xml of \"([^\"]*)\"$")
public void an_HTTP_GET_request_is_sent_to_obtain_config_xml_of(String arg1) throws Throwable {
// Express the Regexp above with the code you wish you had
throw new PendingException();
}
Since I am using groovy, I would like to get something like
#When(/^an HTTP GET request is sent to obtain config.xml of "([^"]*)"$/)
void 'an HTTP GET request is sent to obtain config.xml of'(String arg1) {
// Express the Regexp above with the code you wish you had
throw new PendingException();
}
I am thinking to implement such a feature. Basically, my idea is to add a new field, maybe called templateLanguage, in cucumber.api.CucumberOptions. When this new field is equal to groovy, then the groovy-style templates will be spitted. This will probably involve an if statement in cucumber.runtime.java.JavaSnippet.template(), such as
if( runtimeOptions.getTemplateLanguage().toLowerCase().equals('groovy') ) {...}
However, my question is: how can I get a reference of the runtimeOptions that is passed in like
#CucumberOptions(
format = ["pretty", "html:build/cucumber"],
features="src/test/resources/cucumber_features/api/job_view.feature",
glue=['com.yahoo.adcd.jenkins.tests.smoke.api.cucumber.job.view'],
strict = true
)
Thank you very much!
In a case like this, you would need to write your own boot class since there is no dependency injection for RuntimeOptions. A good starting location is to look at cucumber.api.cli.Main. You would need to create your own class that extends RuntimeOptions, then add in your logic there.
This solution, however, will not allow you run the app using the CucumberOptions annotation anymore. If you do prefer using the annotation though, you would need to also implement your own custom annotation and override the RuntimeOptionsFactory to use your annotation, and then use that factory in your new main class to create the runtime dynamically.