From dde3bbd5e0a7a7358541c957e6cd1c08570bebf9 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Fri, 4 Aug 2023 16:06:32 -0700 Subject: [PATCH] feat(gazelle): generate kt_jvm_binary targets (#2990) GitOrigin-RevId: 762169dfe192a8c5ac31f269fe59908f335ea078 --- gazelle/kotlin/BUILD.bazel | 1 + gazelle/kotlin/generate.go | 50 +++++++++++++-- gazelle/kotlin/kotlin.go | 64 ++++++++++++++----- gazelle/kotlin/language.go | 13 ++++ gazelle/kotlin/parser/parser.go | 6 ++ gazelle/kotlin/parser/parser_test.go | 24 +++++++ gazelle/kotlin/resolver.go | 40 +++++++----- gazelle/kotlin/tests/bin/BUILD.in | 0 gazelle/kotlin/tests/bin/BUILD.out | 19 ++++++ gazelle/kotlin/tests/bin/Hello.kt | 3 + gazelle/kotlin/tests/bin/PkgHello.kt | 7 ++ gazelle/kotlin/tests/bin/WORKSPACE | 2 + gazelle/kotlin/tests/bin/lib.kt | 5 ++ gazelle/kotlin/tests/local_deps/BUILD.in | 1 + gazelle/kotlin/tests/local_deps/BUILD.out | 4 +- .../tests/local_deps/{main.kt => root.kt} | 6 +- gazelle/kotlin/tests/native_deps/BUILD.out | 2 +- .../tests/native_deps/{main.kt => lib.kt} | 2 +- gazelle/kotlin/tests/simple_file/BUILD.out | 4 +- .../tests/simple_file/{main.kt => lib.kt} | 2 +- .../tests/simple_file/{mains.kts => libs.kts} | 2 +- gazelle/kotlin/tests/unknown_imports/BUILD.in | 1 + .../kotlin/tests/unknown_imports/BUILD.out | 10 ++- .../tests/unknown_imports/expectedStdout.txt | 4 ++ gazelle/kotlin/tests/unknown_imports/lib.kt | 5 ++ 25 files changed, 229 insertions(+), 48 deletions(-) create mode 100644 gazelle/kotlin/tests/bin/BUILD.in create mode 100644 gazelle/kotlin/tests/bin/BUILD.out create mode 100644 gazelle/kotlin/tests/bin/Hello.kt create mode 100644 gazelle/kotlin/tests/bin/PkgHello.kt create mode 100644 gazelle/kotlin/tests/bin/WORKSPACE create mode 100644 gazelle/kotlin/tests/bin/lib.kt rename gazelle/kotlin/tests/local_deps/{main.kt => root.kt} (54%) rename gazelle/kotlin/tests/native_deps/{main.kt => lib.kt} (95%) rename gazelle/kotlin/tests/simple_file/{main.kt => lib.kt} (79%) rename gazelle/kotlin/tests/simple_file/{mains.kts => libs.kts} (79%) create mode 100644 gazelle/kotlin/tests/unknown_imports/lib.kt diff --git a/gazelle/kotlin/BUILD.bazel b/gazelle/kotlin/BUILD.bazel index 1193bfa81..bff56173e 100644 --- a/gazelle/kotlin/BUILD.bazel +++ b/gazelle/kotlin/BUILD.bazel @@ -31,6 +31,7 @@ go_library( "@bazel_gazelle//resolve:go_default_library", "@bazel_gazelle//rule:go_default_library", "@com_github_bazel_contrib_rules_jvm//java/gazelle/javaconfig", + "@com_github_emirpasic_gods//maps/treemap", "@com_github_emirpasic_gods//sets/treeset", "@com_github_emirpasic_gods//utils", ], diff --git a/gazelle/kotlin/generate.go b/gazelle/kotlin/generate.go index 2ceb4b48f..67ac8db76 100644 --- a/gazelle/kotlin/generate.go +++ b/gazelle/kotlin/generate.go @@ -5,6 +5,7 @@ import ( "math" "os" "path" + "strings" "sync" gazelle "aspect.build/cli/gazelle/common" @@ -14,6 +15,7 @@ import ( "github.com/bazelbuild/bazel-gazelle/language" "github.com/bazelbuild/bazel-gazelle/resolve" "github.com/bazelbuild/bazel-gazelle/rule" + "github.com/emirpasic/gods/maps/treemap" "github.com/emirpasic/gods/sets/treeset" ) @@ -34,13 +36,25 @@ func (kt *kotlinLang) GenerateRules(args language.GenerateArgs) language.Generat // Collect all source files. sourceFiles := kt.collectSourceFiles(cfg, args) - // TODO: multiple targets (lib, test, ...) - target := NewKotlinTarget() + // TODO: multiple library targets (lib, test, ...) + libTarget := NewKotlinLibTarget() + binTargets := treemap.NewWithStringComparator() // Parse all source files and group information into target(s) for p := range kt.parseFiles(args, sourceFiles) { - target.Files.Add(p.File) - target.Packages.Add(p.Package) + var target *KotlinTarget + + if p.HasMain { + binTarget := NewKotlinBinTarget(p.File, p.Package) + binTargets.Put(p.File, binTarget) + + target = &binTarget.KotlinTarget + } else { + libTarget.Files.Add(p.File) + libTarget.Packages.Add(p.Package) + + target = &libTarget.KotlinTarget + } for _, impt := range p.Imports { target.Imports.Add(ImportStatement{ @@ -55,14 +69,19 @@ func (kt *kotlinLang) GenerateRules(args language.GenerateArgs) language.Generat var result language.GenerateResult - targetName := gazelle.ToDefaultTargetName(args, "root") + libTargetName := gazelle.ToDefaultTargetName(args, "root") + kt.addLibraryRule(libTargetName, libTarget, args, false, &result) - kt.addLibraryRule(targetName, target, args, false, &result) + for _, v := range binTargets.Values() { + binTarget := v.(*KotlinBinTarget) + binTargetName := toBinaryTargetName(binTarget.File) + kt.addBinaryRule(binTargetName, binTarget, args, &result) + } return result } -func (kt *kotlinLang) addLibraryRule(targetName string, target *KotlinTarget, args language.GenerateArgs, isTestRule bool, result *language.GenerateResult) { +func (kt *kotlinLang) addLibraryRule(targetName string, target *KotlinLibTarget, args language.GenerateArgs, isTestRule bool, result *language.GenerateResult) { // TODO: check for rule collisions // Generate nothing if there are no source files. Remove any existing rules. @@ -96,6 +115,23 @@ func (kt *kotlinLang) addLibraryRule(targetName string, target *KotlinTarget, ar BazelLog.Infof("add rule '%s' '%s:%s'", ktLibrary.Kind(), args.Rel, ktLibrary.Name()) } +func (kt *kotlinLang) addBinaryRule(targetName string, target *KotlinBinTarget, args language.GenerateArgs, result *language.GenerateResult) { + main_class := strings.TrimSuffix(target.File, ".kt") + if target.Package != "" { + main_class = target.Package + "." + main_class + } + + ktBinary := rule.NewRule(KtJvmBinary, targetName) + ktBinary.SetAttr("srcs", []string{target.File}) + ktBinary.SetAttr("main_class", main_class) + ktBinary.SetPrivateAttr(packagesKey, target) + + result.Gen = append(result.Gen, ktBinary) + result.Imports = append(result.Imports, target) + + BazelLog.Infof("add rule '%s' '%s:%s'", ktBinary.Kind(), args.Rel, ktBinary.Name()) +} + // TODO: put in common? func (kt *kotlinLang) parseFiles(args language.GenerateArgs, sources *treeset.Set) chan *parser.ParseResult { // The channel of all files to parse. diff --git a/gazelle/kotlin/kotlin.go b/gazelle/kotlin/kotlin.go index 9f289720b..dd43e3de7 100644 --- a/gazelle/kotlin/kotlin.go +++ b/gazelle/kotlin/kotlin.go @@ -1,39 +1,73 @@ package gazelle -import "strings" +import ( + "path" + "strings" +) + import "github.com/emirpasic/gods/sets/treeset" func IsNativeImport(impt string) bool { return strings.HasPrefix(impt, "kotlin.") || strings.HasPrefix(impt, "kotlinx.") || strings.HasPrefix(impt, "java.") || strings.HasPrefix(impt, "javax.") } +type KotlinTarget struct { + Imports *treeset.Set +} + /** * Information for kotlin library target including: - * - BUILD package name + * - kotlin files * - kotlin import statements from all files * - kotlin packages implemented - * - kotlin files with main() methods */ -type KotlinTarget struct { - Name string +type KotlinLibTarget struct { + KotlinTarget - Imports *treeset.Set Packages *treeset.Set - - Mains *treeset.Set - - Files *treeset.Set + Files *treeset.Set } -func NewKotlinTarget() *KotlinTarget { - return &KotlinTarget{ - Imports: treeset.NewWith(importStatementComparator), +func NewKotlinLibTarget() *KotlinLibTarget { + return &KotlinLibTarget{ + KotlinTarget: KotlinTarget{ + Imports: treeset.NewWith(importStatementComparator), + }, Packages: treeset.NewWithStringComparator(), - Mains: treeset.NewWithStringComparator(), Files: treeset.NewWithStringComparator(), } } +/** + * Information for kotlin binary (main() method) including: + * - kotlin import statements from all files + * - the package + * - the file + */ +type KotlinBinTarget struct { + KotlinTarget + + File string + Package string +} + +func NewKotlinBinTarget(file, pkg string) *KotlinBinTarget { + return &KotlinBinTarget{ + KotlinTarget: KotlinTarget{ + Imports: treeset.NewWith(importStatementComparator), + }, + File: file, + Package: pkg, + } +} + // packagesKey is the name of a private attribute set on generated kt_library // rules. This attribute contains the KotlinTarget for the target. -const packagesKey = "_java_packages" +const packagesKey = "_kotlin_package" + +func toBinaryTargetName(mainFile string) string { + base := strings.ToLower(strings.TrimSuffix(path.Base(mainFile), path.Ext(mainFile))) + + // TODO: move target name template to directive + return base + "_bin" +} diff --git a/gazelle/kotlin/language.go b/gazelle/kotlin/language.go index abd3ab93c..fcec59345 100644 --- a/gazelle/kotlin/language.go +++ b/gazelle/kotlin/language.go @@ -13,6 +13,7 @@ const LanguageName = "kotlin" const ( KtJvmLibrary = "kt_jvm_library" + KtJvmBinary = "kt_jvm_binary" RulesKotlinRepositoryName = "io_bazel_rules_kotlin" ) @@ -61,6 +62,17 @@ var kotlinKinds = map[string]rule.KindInfo{ "deps": true, }, }, + + KtJvmBinary: { + MatchAny: false, + NonEmptyAttrs: map[string]bool{ + "srcs": true, + "main_class": true, + }, + SubstituteAttrs: map[string]bool{}, + MergeableAttrs: map[string]bool{}, + ResolveAttrs: map[string]bool{}, + }, } var kotlinLoads = []rule.LoadInfo{ @@ -68,6 +80,7 @@ var kotlinLoads = []rule.LoadInfo{ Name: "@" + RulesKotlinRepositoryName + "//kotlin:jvm.bzl", Symbols: []string{ KtJvmLibrary, + KtJvmBinary, }, }, } diff --git a/gazelle/kotlin/parser/parser.go b/gazelle/kotlin/parser/parser.go index b09291146..99fed7850 100644 --- a/gazelle/kotlin/parser/parser.go +++ b/gazelle/kotlin/parser/parser.go @@ -16,6 +16,7 @@ type ParseResult struct { File string Imports []string Package string + HasMain bool } type Parser interface { @@ -93,6 +94,11 @@ func (p *treeSitterParser) Parse(filePath, source string) (*ParseResult, []error } result.Package = readIdentifier(getLoneChild(nodeI, "identifier"), sourceCode, false) + } else if nodeI.Type() == "function_declaration" { + nodeJ := getLoneChild(nodeI, "simple_identifier") + if nodeJ.Content(sourceCode) == "main" { + result.HasMain = true + } } } diff --git a/gazelle/kotlin/parser/parser_test.go b/gazelle/kotlin/parser/parser_test.go index 130d787ca..ada6d1c5e 100644 --- a/gazelle/kotlin/parser/parser_test.go +++ b/gazelle/kotlin/parser/parser_test.go @@ -69,6 +69,30 @@ func TestTreesitterParser(t *testing.T) { } }) } + + t.Run("main detection", func(t *testing.T) { + res, _ := NewParser().Parse("main.kt", "fun main() {}") + if !res.HasMain { + t.Errorf("main method should be detected") + } + + res, _ = NewParser().Parse("x.kt", ` +package my.demo +fun main() {} + `) + if !res.HasMain { + t.Errorf("main method should be detected with package") + } + + res, _ = NewParser().Parse("x.kt", ` +package my.demo +import kotlin.text.* +fun main() {} + `) + if !res.HasMain { + t.Errorf("main method should be detected with imports") + } + }) } func equal[T comparable](a, b []T) bool { diff --git a/gazelle/kotlin/resolver.go b/gazelle/kotlin/resolver.go index 0bc420721..da077f8c3 100644 --- a/gazelle/kotlin/resolver.go +++ b/gazelle/kotlin/resolver.go @@ -47,22 +47,24 @@ func (*Resolver) Name() string { func (kt *Resolver) Imports(c *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec { BazelLog.Debugf("Imports: '%s:%s'", f.Pkg, r.Name()) - target := r.PrivateAttr(packagesKey).(*KotlinTarget) - - provides := make([]resolve.ImportSpec, 0, target.Packages.Size()) - for _, pkg := range target.Packages.Values() { - provides = append(provides, resolve.ImportSpec{ - Lang: LanguageName, - Imp: pkg.(string), - }) - } + if r.PrivateAttr(packagesKey) != nil { + target, isLib := r.PrivateAttr(packagesKey).(*KotlinLibTarget) + if isLib { + provides := make([]resolve.ImportSpec, 0, target.Packages.Size()) + for _, pkg := range target.Packages.Values() { + provides = append(provides, resolve.ImportSpec{ + Lang: LanguageName, + Imp: pkg.(string), + }) + } - // TODO: why nil instead of just returning empty? - if len(provides) == 0 { - return nil + if len(provides) > 0 { + return provides + } + } } - return provides + return nil } func (kt *Resolver) Embeds(r *rule.Rule, from label.Label) []label.Label { @@ -73,8 +75,16 @@ func (kt *Resolver) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repo.Re start := time.Now() BazelLog.Infof("Resolve '%s' dependencies", from.String()) - if r.Kind() == KtJvmLibrary { - deps, err := kt.resolveImports(c, ix, importData.(*KotlinTarget).Imports, from) + if r.Kind() == KtJvmLibrary || r.Kind() == KtJvmBinary { + var target KotlinTarget + + if r.Kind() == KtJvmLibrary { + target = importData.(*KotlinLibTarget).KotlinTarget + } else { + target = importData.(*KotlinBinTarget).KotlinTarget + } + + deps, err := kt.resolveImports(c, ix, target.Imports, from) if err != nil { log.Fatal("Resolution Error: ", err) os.Exit(1) diff --git a/gazelle/kotlin/tests/bin/BUILD.in b/gazelle/kotlin/tests/bin/BUILD.in new file mode 100644 index 000000000..e69de29bb diff --git a/gazelle/kotlin/tests/bin/BUILD.out b/gazelle/kotlin/tests/bin/BUILD.out new file mode 100644 index 000000000..5719f9738 --- /dev/null +++ b/gazelle/kotlin/tests/bin/BUILD.out @@ -0,0 +1,19 @@ +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary", "kt_jvm_library") + +kt_jvm_library( + name = "bin", + srcs = ["lib.kt"], +) + +kt_jvm_binary( + name = "hello_bin", + srcs = ["Hello.kt"], + main_class = "Hello", +) + +kt_jvm_binary( + name = "pkghello_bin", + srcs = ["PkgHello.kt"], + main_class = "foo.pkg.PkgHello", + deps = [":bin"], +) diff --git a/gazelle/kotlin/tests/bin/Hello.kt b/gazelle/kotlin/tests/bin/Hello.kt new file mode 100644 index 000000000..3708dd833 --- /dev/null +++ b/gazelle/kotlin/tests/bin/Hello.kt @@ -0,0 +1,3 @@ +fun main() { + println("Hello world!") +} \ No newline at end of file diff --git a/gazelle/kotlin/tests/bin/PkgHello.kt b/gazelle/kotlin/tests/bin/PkgHello.kt new file mode 100644 index 000000000..dc676e271 --- /dev/null +++ b/gazelle/kotlin/tests/bin/PkgHello.kt @@ -0,0 +1,7 @@ +package foo.pkg + +import test.lib.* + +fun main() { + println("Hello world from ", Constants.NAME) +} \ No newline at end of file diff --git a/gazelle/kotlin/tests/bin/WORKSPACE b/gazelle/kotlin/tests/bin/WORKSPACE new file mode 100644 index 000000000..d74158eba --- /dev/null +++ b/gazelle/kotlin/tests/bin/WORKSPACE @@ -0,0 +1,2 @@ +# This is a Bazel workspace for the Gazelle test data. +workspace(name = "bin") diff --git a/gazelle/kotlin/tests/bin/lib.kt b/gazelle/kotlin/tests/bin/lib.kt new file mode 100644 index 000000000..bb29d89d5 --- /dev/null +++ b/gazelle/kotlin/tests/bin/lib.kt @@ -0,0 +1,5 @@ +package test.lib + +object Constants { + const val NAME = "bin test" +} \ No newline at end of file diff --git a/gazelle/kotlin/tests/local_deps/BUILD.in b/gazelle/kotlin/tests/local_deps/BUILD.in index e69de29bb..a26f92563 100644 --- a/gazelle/kotlin/tests/local_deps/BUILD.in +++ b/gazelle/kotlin/tests/local_deps/BUILD.in @@ -0,0 +1 @@ +package(default_visibility = ["//visibility:public"]) \ No newline at end of file diff --git a/gazelle/kotlin/tests/local_deps/BUILD.out b/gazelle/kotlin/tests/local_deps/BUILD.out index 2771a1b97..896d1cb33 100644 --- a/gazelle/kotlin/tests/local_deps/BUILD.out +++ b/gazelle/kotlin/tests/local_deps/BUILD.out @@ -1,8 +1,10 @@ load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") +package(default_visibility = ["//visibility:public"]) + kt_jvm_library( name = "local_deps", - srcs = ["main.kt"], + srcs = ["root.kt"], deps = [ "//impt", "//impt-star", diff --git a/gazelle/kotlin/tests/local_deps/main.kt b/gazelle/kotlin/tests/local_deps/root.kt similarity index 54% rename from gazelle/kotlin/tests/local_deps/main.kt rename to gazelle/kotlin/tests/local_deps/root.kt index 31d282317..a858fce06 100644 --- a/gazelle/kotlin/tests/local_deps/main.kt +++ b/gazelle/kotlin/tests/local_deps/root.kt @@ -4,7 +4,7 @@ package test.root import test.impt.Rectangle import test.imptstar.* -fun main() { - A.f() - Rectangle.f() +fun use_imports() { + Rectangle(1.0, 2.0) + Rectangle2(3.0, 4.0) } \ No newline at end of file diff --git a/gazelle/kotlin/tests/native_deps/BUILD.out b/gazelle/kotlin/tests/native_deps/BUILD.out index b86e26690..5887dbfb8 100644 --- a/gazelle/kotlin/tests/native_deps/BUILD.out +++ b/gazelle/kotlin/tests/native_deps/BUILD.out @@ -2,5 +2,5 @@ load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") kt_jvm_library( name = "native_deps", - srcs = ["main.kt"], + srcs = ["lib.kt"], ) diff --git a/gazelle/kotlin/tests/native_deps/main.kt b/gazelle/kotlin/tests/native_deps/lib.kt similarity index 95% rename from gazelle/kotlin/tests/native_deps/main.kt rename to gazelle/kotlin/tests/native_deps/lib.kt index ea003a853..feefcece5 100644 --- a/gazelle/kotlin/tests/native_deps/main.kt +++ b/gazelle/kotlin/tests/native_deps/lib.kt @@ -13,6 +13,6 @@ import kotlinx.serialization.json.Json as Jason @Serializable data class Data(val a: Int, val b: String) -fun main() { +fun other() { val json = Jason.encodeToString(Data(42, "str")) } \ No newline at end of file diff --git a/gazelle/kotlin/tests/simple_file/BUILD.out b/gazelle/kotlin/tests/simple_file/BUILD.out index addf541e1..270482690 100644 --- a/gazelle/kotlin/tests/simple_file/BUILD.out +++ b/gazelle/kotlin/tests/simple_file/BUILD.out @@ -3,7 +3,7 @@ load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") kt_jvm_library( name = "simple_file", srcs = [ - "main.kt", - "mains.kts", + "lib.kt", + "libs.kts", ], ) diff --git a/gazelle/kotlin/tests/simple_file/main.kt b/gazelle/kotlin/tests/simple_file/lib.kt similarity index 79% rename from gazelle/kotlin/tests/simple_file/main.kt rename to gazelle/kotlin/tests/simple_file/lib.kt index 4130457ff..1367bdf69 100644 --- a/gazelle/kotlin/tests/simple_file/main.kt +++ b/gazelle/kotlin/tests/simple_file/lib.kt @@ -1,5 +1,5 @@ // Hello World Program -fun main() { +fun hello() { println("Hello world!") } \ No newline at end of file diff --git a/gazelle/kotlin/tests/simple_file/mains.kts b/gazelle/kotlin/tests/simple_file/libs.kts similarity index 79% rename from gazelle/kotlin/tests/simple_file/mains.kts rename to gazelle/kotlin/tests/simple_file/libs.kts index 4130457ff..1367bdf69 100644 --- a/gazelle/kotlin/tests/simple_file/mains.kts +++ b/gazelle/kotlin/tests/simple_file/libs.kts @@ -1,5 +1,5 @@ // Hello World Program -fun main() { +fun hello() { println("Hello world!") } \ No newline at end of file diff --git a/gazelle/kotlin/tests/unknown_imports/BUILD.in b/gazelle/kotlin/tests/unknown_imports/BUILD.in index e69de29bb..a26f92563 100644 --- a/gazelle/kotlin/tests/unknown_imports/BUILD.in +++ b/gazelle/kotlin/tests/unknown_imports/BUILD.in @@ -0,0 +1 @@ +package(default_visibility = ["//visibility:public"]) \ No newline at end of file diff --git a/gazelle/kotlin/tests/unknown_imports/BUILD.out b/gazelle/kotlin/tests/unknown_imports/BUILD.out index 2d2af8416..5ac4ff47b 100644 --- a/gazelle/kotlin/tests/unknown_imports/BUILD.out +++ b/gazelle/kotlin/tests/unknown_imports/BUILD.out @@ -1,6 +1,14 @@ -load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary", "kt_jvm_library") + +package(default_visibility = ["//visibility:public"]) kt_jvm_library( name = "simple_file", + srcs = ["lib.kt"], +) + +kt_jvm_binary( + name = "main_bin", srcs = ["main.kt"], + main_class = "main", ) diff --git a/gazelle/kotlin/tests/unknown_imports/expectedStdout.txt b/gazelle/kotlin/tests/unknown_imports/expectedStdout.txt index 7d8ea5bea..95d524094 100644 --- a/gazelle/kotlin/tests/unknown_imports/expectedStdout.txt +++ b/gazelle/kotlin/tests/unknown_imports/expectedStdout.txt @@ -1,3 +1,7 @@ +Resolution error Import "lib.not" from "lib.kt" is an unknown dependency. Possible solutions: + 1. Instruct Gazelle to resolve to a known dependency using a directive: + # gazelle:resolve [src-lang] kotlin import-string label + Resolution error Import "foo" from "main.kt" is an unknown dependency. Possible solutions: 1. Instruct Gazelle to resolve to a known dependency using a directive: # gazelle:resolve [src-lang] kotlin import-string label diff --git a/gazelle/kotlin/tests/unknown_imports/lib.kt b/gazelle/kotlin/tests/unknown_imports/lib.kt new file mode 100644 index 000000000..e70ea4e5d --- /dev/null +++ b/gazelle/kotlin/tests/unknown_imports/lib.kt @@ -0,0 +1,5 @@ +import lib.not.found + +fun lib() { + +} \ No newline at end of file