Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .idea/dictionaries/project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"mcpServers": {
"youtrack": {
"type": "http",
"url": "https://youtrack.jetbrains.com/mcp",
"headers": {
"Authorization": "Bearer ${YOUTRACK_TOKEN}"
}
}
}
}
16 changes: 16 additions & 0 deletions docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ In the root of the project you should create a `local.properties` file with the
sdk.dir=/Users/<user>/Library/Android/sdk
```

## YouTrack MCP
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, what can I do with it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, work with YT from agents

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can build the whole workflow: find issues -> create reproducer -> try fix -> create PR


The project includes a `.mcp.json` that connects Claude Code to YouTrack via MCP.
To authenticate, set the `YOUTRACK_TOKEN` environment variable.

1. Go to https://youtrack.jetbrains.com/users/me
2. Navigate to **Account Security** -> **Tokens** -> **New token...**
3. Select **YouTrack** as the scope, create the token and copy it
4. Add to your `.zshrc` (or equivalent):

```bash
export YOUTRACK_TOKEN="your-token-here"
```

5. Restart your shell (or `source ~/.zshrc`)

## Git and GitHub

Make sure your commit signing is set up. Check the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package util.tasks

import org.gradle.api.Project
import org.gradle.api.tasks.Copy
import org.gradle.kotlin.dsl.accessors.runtime.addExternalModuleDependencyTo
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.register
import util.other.libs

private const val PROTOBUF_SOURCE_ARCHIVE_CONFIGURATION = "protobufSourceArchive"
private const val EXTRACT_UNITTEST_PROTOS_TASK = "extractUnittestProtos"
private const val EXTRACT_UNITTEST_PROTO_IMPORTS_TASK = "extractUnittestProtoImports"

fun Project.setupProtobufUnittestProtos() {
val protobufVersion = libs.versions.protobuf.asProvider().get().substringAfter(".")

// Ivy repository for GitHub releases (source archive has no platform classifier)
repositories.ivy {
name = "github-source-archive"
url = uri("https://github.com")

patternLayout {
// https://github.com/protocolbuffers/protobuf/releases/download/v31.1/protobuf-31.1.zip
artifact("[organisation]/[module]/releases/download/v[revision]/[artifact]-[revision].[ext]")
}

metadataSources { artifact() }
}

configurations.create(PROTOBUF_SOURCE_ARCHIVE_CONFIGURATION)

dependencies {
addExternalModuleDependencyTo(
dependencyHandler = this,
targetConfiguration = PROTOBUF_SOURCE_ARCHIVE_CONFIGURATION,
group = "protocolbuffers",
name = "protobuf",
version = protobufVersion,
classifier = null,
ext = null,
configuration = null,
) {
artifact {
name = "protobuf"
type = "zip"
extension = "zip"
}
}
}

val archivePrefix = "protobuf-$protobufVersion/src/"

val protosDir = project.layout.buildDirectory.dir("protobuf-unittest-protos")

// Extract only unittest-related proto files (not well-known types which buf handles natively)
val extractUnittestProtos = tasks.register<Copy>(EXTRACT_UNITTEST_PROTOS_TASK) {
val archiveFiles = configurations.getByName(PROTOBUF_SOURCE_ARCHIVE_CONFIGURATION)

from(archiveFiles.map { zipTree(it) })

include(
"${archivePrefix}google/protobuf/unittest*.proto",
"${archivePrefix}google/protobuf/map_*unittest*.proto",
"${archivePrefix}google/protobuf/edition_unittest*.proto",
)

eachFile {
path = path.removePrefix(archivePrefix)
}

includeEmptyDirs = false
into(protosDir)
}

val importsDir = project.layout.buildDirectory.dir("protobuf-unittest-imports")

// Extract non-WKT import dependencies (e.g., cpp_features.proto) as import-only files
val extractUnittestProtoImports = tasks.register<Copy>(EXTRACT_UNITTEST_PROTO_IMPORTS_TASK) {
val archiveFiles = configurations.getByName(PROTOBUF_SOURCE_ARCHIVE_CONFIGURATION)

from(archiveFiles.map { zipTree(it) })

include("${archivePrefix}google/protobuf/cpp_features.proto")

eachFile {
path = path.removePrefix(archivePrefix)
}

includeEmptyDirs = false
into(importsDir)
}

tasks.matching { it.name == "processMainProtoFiles" }.all {
dependsOn(extractUnittestProtos)
}

tasks.matching { it.name == "processMainProtoFilesImports" }.all {
dependsOn(extractUnittestProtoImports)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ sealed interface FqName {
val String = fq("kotlin", "String")
val Int = fq("kotlin", "Int")
val Float = fq("kotlin", "Float")
val Float_Nan = Float.nested("Nan")
val Double = fq("kotlin", "Double")
val Double_Nan = Double.nested("Nan")
val Boolean = fq("kotlin", "Boolean")
val ByteArray = fq("kotlin", "ByteArray")
val List = fq("kotlin.collections", "List")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1736,7 +1736,7 @@ class ModelToProtobufKotlinCommonGenerator(
}

value is Float -> when {
value.isNaN() -> FqName.Implicits.Float_Nan.scoped()
value.isNaN() -> "%T.NaN".scoped(FqName.Implicits.Float)
value == Float.POSITIVE_INFINITY -> "%T.POSITIVE_INFINITY".scoped(FqName.Implicits.Float)
value == Float.NEGATIVE_INFINITY -> "%T.NEGATIVE_INFINITY".scoped(FqName.Implicits.Float)
else -> FqName.Implicits.Float.scoped().wrapIn { float ->
Expand All @@ -1746,7 +1746,7 @@ class ModelToProtobufKotlinCommonGenerator(
}

value is Double -> when {
value.isNaN() -> FqName.Implicits.Double_Nan.scoped()
value.isNaN() -> "%T.NaN".scoped(FqName.Implicits.Double)
value == Double.POSITIVE_INFINITY -> "%T.POSITIVE_INFINITY".scoped(FqName.Implicits.Double)
value == Double.NEGATIVE_INFINITY -> "%T.NEGATIVE_INFINITY".scoped(FqName.Implicits.Double)
else -> FqName.Implicits.Double.scoped().wrapIn { double ->
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ if (!onWindows) {
include(":tests:protobuf-conformance")
}

include(":tests:protobuf-unittest")

val kotlinMasterBuild = providers.gradleProperty("kotlinx.rpc.kotlinMasterBuild").orNull == "true"

if (!kotlinMasterBuild) {
Expand Down
61 changes: 61 additions & 0 deletions tests/protobuf-unittest/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

@file:OptIn(InternalRpcApi::class)

import kotlinx.rpc.internal.InternalRpcApi
import kotlinx.rpc.internal.configureLocalProtocGenDevelopmentDependency
import kotlinx.rpc.protoc.buf
import kotlinx.rpc.protoc.generate
import kotlinx.rpc.protoc.kotlinMultiplatform
import kotlinx.rpc.protoc.proto
import kotlinx.rpc.protoc.protoTasks
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
import util.tasks.setupProtobufUnittestProtos

plugins {
alias(libs.plugins.conventions.jvm)
alias(libs.plugins.kotlinx.rpc)
}

kotlin {
explicitApi = ExplicitApiMode.Disabled
}

dependencies {
implementation(libs.coroutines.core)
implementation(projects.protobuf.protobufCore)
}

setupProtobufUnittestProtos()
configureLocalProtocGenDevelopmentDependency("Main")

// Only use the protobuf plugin (no gRPC generation needed for unittest protos)
// Add the extracted proto files from build directory as a proto source
sourceSets.main.get().proto {
srcDir(layout.buildDirectory.dir("protobuf-unittest-protos"))

// Import non-WKT dependencies (e.g., cpp_features.proto) without generating code for them
fileImports.from(layout.buildDirectory.dir("protobuf-unittest-imports"))

// Exclude protos incompatible with buf, exceeding JVM limits, or triggering generator bugs:
exclude(
// buf incompatibilities:
"**/unittest_custom_options.proto", // legacy 'message set wire format' (proto1 feature)
"**/unittest_lite_edition_2024.proto", // edition "2024" not yet supported
// JVM method size limit:
"**/unittest_enormous_descriptor.proto", // generated method exceeds 64KB
// Generator bugs (incorrect code generation for complex proto2/edition features):
"**/unittest.proto", // groups, extensions, nested types — many codegen issues
"**/edition_unittest.proto", // edition 2023 equivalent of unittest.proto
"**/unittest_optimize_for.proto", // depends on unittest.proto
"**/unittest_embed_optimize_for.proto", // depends on unittest_optimize_for.proto
"**/unittest_no_field_presence.proto", // depends on unittest.proto
"**/unittest_lite_imports_nonlite.proto", // depends on unittest.proto
"**/map_unittest.proto", // depends on unittest.proto
)

plugins.empty()
plugin(rpc.protoc.get().plugins.kotlinMultiplatform)
}
Loading