I'm writing a custom Gradle plugin that registers an extension and uses Actions for configuring it so you can use it like
myExtension {
info {
someConfig("a") {
// config for someConfig["a"]
}
someConfig("b") {
// config for someConfig["b"]
}
}
}
Based on how much someConfig you define, the plugin created new Gradle tasks dynamically. I've seen some scenarios in which it'd useful to configure all someConfig with the same values, so instead of writing
myExtension {
info {
someConfig("a") {
someValue = x
}
someConfig("b") {
someValue = x
}
}
}
I was thinking in providing something like
myExtension {
info {
someConfig("a")
someConfig("b")
allConfigs { configName ->
someValue = x
}
}
}
When trying to code it, I've used an objectFactory for creating instances of Action so I can configure the extension values
val allMyConfigs = mutableMapOf<String, SomeConfig>()
fun someConfig(configName: String, init: Action<in EnvironmentInfo>) {
val myConfig = allMyConfigs.computeIfAbsent(env) {
objectFactory.newInstance(SomeConfig::class.java, env)
}
init.execute(environmentInfo)
}
but since Action is a (SomeConfig) -> Unit (i.e. a function receiving a SomeConfig instance) I'm unable to pass the config name (an String) as param
Is there a way to create an Action, so it's compatible with both build.gradle and build.gradle.kts files, that receives an extra parameter and maintain the SomeConfig as delegate?
Related
I have a proto with the following definitions.
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions{
optional bool is_key = 50002;
}
message Foo{
int64 id = 1 [(is_key) = true];
}
I generated a .desc file for the above. I was able to access all the Fields and Message defined by the FieldDescriptorProto and DescriptorProto types but not sure how to access the options defined and the value provided to it in this case is_key.
Could anyone provide me with a java version that could access the options from the .desc file
Not sure If this is the right way but all I was trying to do was read custom options from a descriptor_set.desc file.
I parsed the desc file, the problem is that it returns fileDescriptorProto Types. So I parsed them and built a dependencies graph. This helped me to create FileDescriptor Types.
Through FileDescriptor Types I was able to get Extensions and add them to extensionRegistry.
Post Which I parsed the .desc file once again but this time with extensionRegistry.
val filePathToFileDescriptorMap = mutableMapOf <String, FileDescriptor>(DescriptorProtos.getDescriptor().file.name to
DescriptorProtos.getDescriptor())
val extensionRegistry = ExtensionRegistry.newInstance()
fun buildDependencies(fileDescriptorProto: DescriptorProtos.FileDescriptorProto,
filePathToFileDescriptorProtoMap: Map<String, DescriptorProtos.FileDescriptorProto>) : FileDescriptor {
val dependencies = fileDescriptorProto.dependencyList.map { dependency ->
if(filePathToFileDescriptorMap.containsKey(dependency)) {
filePathToFileDescriptorMap[dependency]
}
else if(!filePathToFileDescriptorProtoMap.containsKey(dependency)) {
throw Exception("dependency not found $dependency")
}
else {
buildDependencies(filePathToFileDescriptorProtoMap[dependency]!!, filePathToFileDescriptorProtoMap)
}
}
filePathToFileDescriptorMap[fileDescriptorProto.name] =
FileDescriptor.buildFrom(fileDescriptorProto, dependencies.toTypedArray())
filePathToFileDescriptorMap[fileDescriptorProto.name]!!.extensions.forEach {
if(it.type == Descriptors.FieldDescriptor.Type.MESSAGE){
extensionRegistry.add(it, DynamicMessage.newBuilder(it.messageType).build())
}else{
extensionRegistry.add(it)
}
}
return filePathToFileDescriptorMap[fileDescriptorProto.name]!!
}
fun registerExtensions(){
val fileDescriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(
FileInputStream("src/generated/resources/desc_set.desc"),
)
val filePathToFileDescriptorProtoMap = fileDescriptorSet.fileList.associateBy { it.name }
fileDescriptorSet.fileList.forEach { fileDescriptorProto ->
if(!filePathToFileDescriptorMap.containsKey(fileDescriptorProto.name)){
buildDependencies(fileDescriptorProto, filePathToFileDescriptorProtoMap)
}
}
val fileDescriptorSetWithOptions = DescriptorProtos.FileDescriptorSet.parseFrom(
FileInputStream("src/generated/resources/desc_set.desc"),
extensionRegistry
)
println(fileDescriptorSetWithOptions)
}
I would like to transform some dependencies when Gradle resolves them.
I have followed the 'Transforming dependency artifacts on resolution' documentation, but my TransformAction is not triggered.
I have
created a custom configuration, gitHub
created custom attributes, and both
applied them to the gitHub configuration,
and registered them in the dependencies {} block)
created and registered a custom TransformAction.
created a task, resolveGitHubDependencies, that will resolve the gitHub configuration.
I have tried a few different adjustments, but nothing seems to have an impact.
What am I missing? Or is this a bug?
Here's the code I've created. It can be tested by running ./gradlew resolveGitHubDependencies. I expect that MyTransformer will print some log statements, but it doesn't print anything.
import org.slf4j.LoggerFactory
plugins {
base
}
repositories {
ivy("https://github.com/") {
name = "GitHub"
patternLayout {
artifact("/[organisation]/[module]/archive/[revision].[ext]")
artifact("/[organisation]/[module]/releases/download/[revision]/[module](-[revision]).[ext]")
}
metadataSources { artifact() }
}
}
// from https://github.com/gradle/gradle/issues/19707
val transformed = Attribute.of("my-library.transformed", Boolean::class.javaObjectType)
val dependencySource = Attribute.of("dependency source", String::class.java)
// my custom configuration - I want to transform all dependencies from this configuration
val gitHub by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
attributes {
attribute(dependencySource, "gitHub")
attribute(transformed, false)
}
}
// this is the transformer that I expect to be triggered on dependency resolution
abstract class MyTransformer : TransformAction<MyTransformer.Parameters> {
private val logger = LoggerFactory.getLogger(this::class.java)
interface Parameters : TransformParameters {
#get:Input
val sourceAttributes: MapProperty<Attribute<*>, Any?>
}
#get:PathSensitive(PathSensitivity.NAME_ONLY)
#get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
override fun transform(outputs: TransformOutputs) {
val sourceAttributes = parameters.sourceAttributes.get()
logger.error("sourceAttributes: $sourceAttributes")
logger.error("sourceAttributes: ${sourceAttributes.keys}")
logger.error("sourceAttributes: ${sourceAttributes.values.joinToString { it.toString() }}")
val input = inputArtifact.get().asFile
outputs.file(input)
}
}
dependencies {
// this is the dependency I want to transform
gitHub("emcrisostomo:fswatch:1.17.1#tar.gz")
attributesSchema {
attribute(dependencySource)
attribute(transformed)
}
// artifactTypes.configureEach {
// attributes.attribute(transformed, false)
// }
registerTransform(MyTransformer::class) {
// from.attributes.attribute(dependencySource, "gitHub")
// to.attributes.attribute(dependencySource, "gitHub")
from.attributes.attribute(dependencySource, "gitHub").attribute(transformed, false)
to.attributes.attribute(dependencySource, "gitHub").attribute(transformed, true)
parameters {
sourceAttributes.set(
from.attributes.keySet().associateWith { key ->
from.attributes.getAttribute(key)
}
)
}
}
}
val resolveGitHubDependencies by tasks.registering(Sync::class) {
group = "gh transform"
from(gitHub)
into(temporaryDir)
}
Gradle version: 7.6
I have a multiplatform library to do few APIs calls with Ktor (2.0.0-beta-1)
class DiscoveryServicesImpl(private val client: HttpClient) : DiscoveryServices {
override suspend fun getServers(domain: String): DAAServers {
return client.get(Servers.Domain(domain = domain)).body()
}
}
// Where client is created with specific engine (OkHttp for Android and Darwin for iOS)
This code works as expected (implemented in both Android and iOS apps)
To secure this code, I've added a unit test to verify that the url is well builded.
#Test
fun getServersSuccessful() {
runBlocking {
var _request: HttpRequestData? = null
//Given
val mockEngine = MockEngine {
_request = it
}
val discoveryApiMock = DiscoveryApi(mockEngine)
val service = DiscoveryServicesImpl(discoveryApiMock.client)
//When
val servers: DAAServers = service.getServers(domain)
//Then
assertEquals("https://****/$domain", _request!!.url.toString())
}
}
But when a run this test I got an error (for iOS test only) :
> Task :api:discovery:iosTest
***.discovery.DiscoveryServicesImplTest.getServersSuccessful FAILED
kotlin.native.concurrent.InvalidMutabilityException at /Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:24
kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlin.native.internal.Ref#15cb7c8
kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlin.native.internal.Ref#15cb7c8
at kotlin.Throwable#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:24)
at kotlin.Exception#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:23)
at kotlin.RuntimeException#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:34)
at kotlin.native.concurrent.InvalidMutabilityException#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:24)
at <global>.ThrowInvalidMutabilityException(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:109)
at <global>.MutationCheck(Unknown Source)
at kotlin.native.internal.Ref#<set-element>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Ref.kt:12)
It's happening only on the ios target
To fix this, I've added this code pretty much everywhere (commonMain, commonTest, iosMain, iosTest) but it doesn't change anything
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt") {
version {
strictly("1.6.0-native-mt")
}
}
Below, my gradles files :
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(Ktor.clientCore)
implementation(Ktor.clientResources)
implementation(Ktor.contentNegotiation)
implementation(Ktor.clientJson)
implementation(Ktor.clientLogging)
implementation(Ktor.clientAuth)
implementation(Koin.koinMultiplatform)
implementation(project(":serialization"))
implementation(project(":transverse:log"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt") {
version {
strictly("1.6.0-native-mt")
}
}
}
}
val commonTest by getting {
dependencies {
implementation(Ktor.clientMock)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0-native-mt") {
version {
strictly("1.6.0-native-mt")
}
}
}
}
val iosMain by getting {
dependencies {
implementation(Ktor.clientDarwin)
}
}
}
}
If you're using the latest version of ktor and coroutines, you'll probably want to use the new memory model, and just the 1.6.0 kotlinx.coroutines rather than the native-mt version.
https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/
https://github.com/touchlab/KaMPKit/blob/main/gradle.properties#L26
Most library dev from Jetbrains will likely focus on the new memory model from now on, so unless there are specific near term concerns on moving, I would do that.
The TornadoFX docs describe using the ListCellFragment to bind each cell in a list control to each item model in a list. I am wondering how to do something similar in a flowpane. I'd like to use that kind of class to render a bunch of controls and an SVG drawing in each cell. (So it would replace the button component in the example code below and somehow bind the shapeItem model to it).
class LibraryView : View("Current Library") {
val shapeLibraryViewModel : LibraryViewModel by inject()
override val root = anchorpane{
flowpane {
bindChildren(shapeLibraryViewModel.libraryItemsProperty){
shapeItem -> button(shapeItem.nameProperty)
}
}
}
}
Since I don't see a pre-made class like the one for list view, perhaps I would need to create something similar to it...or maybe there's a more lightweight approach?
Using an ItemFragment is a bit overkill, since the itemProperty of the fragment will never change (a new fragment would be created for every item whenever the libraryItemsProperty change. However, if your view logic for each item is substantial, this approach will provide a clean way to separate and contain that logic, so it might be worth it. Here is a complete example you can use as a starting point.
class ShapeItemFragment : ItemFragment<ShapeItem>() {
val shapeModel = ShapeItemModel().bindTo(this)
override val root = stackpane {
label(shapeModel.name)
}
}
class ShapeItem(name: String) {
val nameProperty = SimpleStringProperty(name)
}
class ShapeItemModel : ItemViewModel<ShapeItem>() {
val name = bind(ShapeItem::nameProperty)
}
class LibraryViewModel : ViewModel() {
val libraryItemsProperty = SimpleListProperty<ShapeItem>(
listOf(
ShapeItem("Shape 1"),
ShapeItem("Shape 2")
).observable()
)
}
class LibraryView : View("Current Library") {
val shapeLibraryViewModel: LibraryViewModel by inject()
override val root = anchorpane {
flowpane {
bindChildren(shapeLibraryViewModel.libraryItemsProperty) { shapeItem ->
val itemFragment = find<ShapeItemFragment>()
itemFragment.itemProperty.value = shapeItem
itemFragment.root
}
}
}
}
A slightly lighter version would be to pass the parameter into your fragment manually and just extend Fragment:
class ShapeItemFragment : Fragment() {
val item: ShapeItem by param()
override val root = stackpane {
label(item.nameProperty)
}
}
You can still bind to changes to properties inside the ShapeItem, since the underlying item won't change (as seen from the ItemFragment) anyway.
Your bindChildren statement would then look like this:
bindChildren(shapeLibraryViewModel.libraryItemsProperty) { shapeItem ->
find<ShapeItemFragment>(ShapeItemFragment::item to shapeItem).root
}
Here is the Gradle documentation, which I don't understand:
idea {
//if you want parts of paths in resulting files (*.iml, etc.) to be replaced by variables (Files)
pathVariables GRADLE_HOME: file('~/cool-software/gradle')
module {
//if for some reason you want to add an extra sourceDirs
sourceDirs += file('some-extra-source-folder')
It sets idea.module.sourceDirs value.
How do I implement it in pure Groovy without Gradle? I want to know its underlying merchanism.
thanks #Steinar, I use following code fix:
def file(String p) {
new File(p)
}
class Idea {
class Module {
Set<File> excludeDirs = []
}
void module(Closure c) {
c.delegate = new Module()
c()
}
}
void idea(Closure c) {
c.delegate = new Idea()
c()
}
idea {
module {
excludeDirs += file("smth")
}
}
With your proposed solution #asullaherc you can't choose between calling idea { module and idea.module { ... so I would rather go for a different solution.
This is the base class for all of the closures you want to create for your case:
class SectionBase extends Closure<Object> {
SectionClosure() {
super(null)
}
Object doCall(final Closure inner) {
inner.delegate = this
inner()
}
}
And this would be an implementation ("Gradle style"):
class DependenciesSection extends SectionBase {
void compile(packageName) {
println "compile: $packageName"
}
void testCompile(packageName) {
println "testCompile: $packageName"
}
}
Now we make an instance:
def dependencies = new DependenciesSection()
And mess around with it a little:
// You can do it either this way...
dependencies {
compile 'com.my.package1'
compile 'com.my.package2'
testCompile 'com.my.package3'
}
// ...or also the other
dependencies.compile 'com.my.package4'
dependencies.testCompile 'com.my.package5'
The output would be:
compile: com.my.package1
compile: com.my.package2
testCompile: com.my.package3
compile: com.my.package4
testCompile: com.my.package5