Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented package mapping for Java/Kotlin code generation #499

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

netvl
Copy link

@netvl netvl commented May 17, 2024

See #456 for motivation.

  • Implement package mapping in Java codegen, add tests
  • Implement package mapping in Kotlin codegen, add tests
  • Propagate configuration to the Gradle plugin

Closes #456

@netvl
Copy link
Author

netvl commented May 17, 2024

@bioball - I've started the package mapping implementation, just wanted to show the overall direction before I start implementing it for Kotlin. What do you think?

Copy link
Contributor

@bioball bioball left a comment

Choose a reason for hiding this comment

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

Generally looks on the right track!

* When this option is set, the mapping will be used to translate package names derived from
* module names to the specified package names.
*/
val packageMapping: String? = null
Copy link
Contributor

Choose a reason for hiding this comment

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

This should accept a structured Map<String, String>; trace how externalProperties and environmentVariables get populated from the CLI args parser.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the pointer, I hoped that there is something like that!

@netvl netvl marked this pull request as ready for review May 30, 2024 20:23
Copy link
Contributor

@bioball bioball left a comment

Choose a reason for hiding this comment

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

This looks pretty good! I am thinking we should adjust how the mapping works, though (see comment in review)

@@ -108,6 +109,18 @@ class PklJavaCodegenCommand :
)
.flag()

private val packageMapping: Map<String, String> by
option(
names = arrayOf("-m", "--package-mapping"),
Copy link
Contributor

Choose a reason for hiding this comment

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

We tend to be conservative about which flags get short flag names, because they quickly pollute the namespace.

I think this one should just be kept as a long flag name.

Suggested change
names = arrayOf("-m", "--package-mapping"),
names = arrayOf("--package-mapping"),

@@ -127,6 +129,9 @@ class JavaCodeGeneratorTest {
}
}

@TempDir
lateinit var tempDir: Path
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this moved to the test suite level?

Copy link
Author

Choose a reason for hiding this comment

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

Two reasons:

  1. tempDir is used by helper methods below, which makes test code simpler compared to constant passing of tempDir as an argument.
  2. Removed repetition because this parameter was present on lots of test methods.

In general, there is not much difference between a field and a method parameter for this use case. JUnit Jupiter always creates a fresh instance of the test class for each test, and it always populates the marked fields before the tests are run, so defining temp dir as a variable allows to simplify some of the helper methods logic and reduce duplication.

)

// No more files.
assertThat(files).isEmpty()
Copy link
Contributor

@bioball bioball Jun 4, 2024

Choose a reason for hiding this comment

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

Nit: I typically write these tests this way, because it's clearer what the assertion is and helps with the error message when it fails.

assertThat(files).containsOnlyKeys("foo", "bar", "bar")
assertThat(files["foo"]).contains("something text")
assertThat(files["bar"]).contains("something text")

This isn't a blocker, though, feel free to just keep this as-is.

Copy link
Author

Choose a reason for hiding this comment

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

I personally don't have much preference either; I chose the current style because this way, I can avoid repeating the map keys.

@@ -872,6 +870,15 @@ class JavaCodeGenerator(
} else key
}
}

private fun mapPackageName(name: String): String =
codegenOptions.packageMapping[name] ?: name
Copy link
Contributor

Choose a reason for hiding this comment

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

I think instead of doing an exact match, this should be a prefix match. That would make this functionality a lot more useful. Also, with an exact match, this seems a little brittle. If a new Pkl module gets added at some subpath, it will fail to match and thus not be mapped to a custom name.

Prefix everything:

mapOf("" to "com.foo.")

Prefix everything starting with "foo." to "com.foo":

mapOf("foo." to "com.foo.")

I think the logic should work like so:

  1. Sort all the mapping entries by length (greatest first)
  2. Find the first entry that matches by prefix

This way, the most specific prefix has greater precedence; the "com.foo" pattern beats the "com" pattern.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, this definitely does make sense, and makes the feature more powerful and useful, thanks for the suggestion!

private fun writePklFile() {
writeFile(
"mod.pkl", """
module org.mod

class Person {
name: String
addresses: List<Address>
addresses: List<Address?>
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this changed?

Copy link
Author

Choose a reason for hiding this comment

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

Previously, there was two almost identical Pkl scripts here, whose difference was basically in this ?. The question mark was necessary, because apparently the code generated without this question mark fails to compile with some really strange error about annotations. I unified these two scripts to avoid repetition, and to make sure that the code can be compiled, I switched it to be nullable. I think it does not matter much here because we are not really concerned with the actual contents of the generated classes, we check that the Gradle plugin works and can be used to generate and compile code properly.


private val packageMapping: Map<String, String> by
option(
names = arrayOf("-m", "--package-mapping"),
Copy link
Contributor

Choose a reason for hiding this comment

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

See comment earlier about short names

Suggested change
names = arrayOf("-m", "--package-mapping"),
names = arrayOf("--package-mapping"),


When you need the generated package names to be different from the default package names derived from Pkl module name prefixes, you can define a package mapping, where the key is the original Pkl module name prefix, and the value is its replacement. When you do, the generated code `package` declarations, as well as file locations, will be modified according to the mapping.

Repeat this option to define multiple mappings. Keys must be valid Pkl module name prefixes. Values must be valid dot-separated package names.
Copy link
Contributor

Choose a reason for hiding this comment

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

Technically, keys can be anything; and if they don't match anything, they are ignored.

Suggested change
Repeat this option to define multiple mappings. Keys must be valid Pkl module name prefixes. Values must be valid dot-separated package names.
Repeat this option to define multiple mappings. Values must be valid dot-separated package names.

Also: our style here is to separate every sentence to be on their own line.

Copy link
Author

Choose a reason for hiding this comment

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

I thought I definitely saw some paragraphs with multiple sentences in one line, that's why I was not concerned about it. I will check again and fix it.

@bioball
Copy link
Contributor

bioball commented Jun 5, 2024

FYI @sgammon: you might be interested in this.

@sgammon
Copy link
Contributor

sgammon commented Jun 5, 2024

@bioball yes, I very much am! thanks for the tag. I'd be happy to test this if helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow overriding Java/Kotlin package name in codegen
3 participants