From cb1be5c659598efef0dd53383193e656822c9543 Mon Sep 17 00:00:00 2001 From: Simon Stewart Date: Fri, 12 Mar 2021 15:12:57 +0000 Subject: [PATCH 1/3] Introduce ktlint_test and ktlint_fix These rules are the first step in providing a lightweight way of ensuring projects use consistent formatting. --- examples/trivial/app/BUILD.bazel | 12 +- kotlin/internal/lint/BUILD.bazel | 0 kotlin/internal/lint/editorconfig.bzl | 4 + kotlin/internal/lint/ktlint_config.bzl | 23 +++ kotlin/internal/lint/ktlint_fix.bzl | 135 ++++++++++++++++++ kotlin/internal/lint/ktlint_test.bzl | 86 +++++++++++ .../repositories/release_repositories.bzl | 9 ++ kotlin/kotlin.bzl | 6 + kotlin/rules.bzl | 6 + 9 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 kotlin/internal/lint/BUILD.bazel create mode 100644 kotlin/internal/lint/editorconfig.bzl create mode 100644 kotlin/internal/lint/ktlint_config.bzl create mode 100644 kotlin/internal/lint/ktlint_fix.bzl create mode 100644 kotlin/internal/lint/ktlint_test.bzl diff --git a/examples/trivial/app/BUILD.bazel b/examples/trivial/app/BUILD.bazel index 7a8df7cf4..3669fcfa8 100644 --- a/examples/trivial/app/BUILD.bazel +++ b/examples/trivial/app/BUILD.bazel @@ -1,4 +1,4 @@ -load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library", "ktlint_fix", "ktlint_test") kt_jvm_library( name = "app_lib", @@ -8,6 +8,16 @@ kt_jvm_library( ], ) +ktlint_test( + name = "lint_test", + srcs = glob(["**/*.kt"]), +) + +ktlint_fix( + name = "lint_fix", + srcs = glob(["**/*.kt"]), +) + java_binary( name = "myapp", main_class = "app.MyApp", diff --git a/kotlin/internal/lint/BUILD.bazel b/kotlin/internal/lint/BUILD.bazel new file mode 100644 index 000000000..e69de29bb diff --git a/kotlin/internal/lint/editorconfig.bzl b/kotlin/internal/lint/editorconfig.bzl new file mode 100644 index 000000000..09bf47a43 --- /dev/null +++ b/kotlin/internal/lint/editorconfig.bzl @@ -0,0 +1,4 @@ +load(":ktlint_config.bzl", "KtlintConfigInfo") + +def get_editorconfig(config): + return config[KtlintConfigInfo].editorconfig if config else None \ No newline at end of file diff --git a/kotlin/internal/lint/ktlint_config.bzl b/kotlin/internal/lint/ktlint_config.bzl new file mode 100644 index 000000000..a0c2c02cd --- /dev/null +++ b/kotlin/internal/lint/ktlint_config.bzl @@ -0,0 +1,23 @@ +KtlintConfigInfo = provider( + fields = { + "editorconfig": "Editor config file to use", + }, +) + +def _ktlint_config_impl(ctx): + return [ + KtlintConfigInfo( + editorconfig = ctx.file.editorconfig, + ), + ] + +ktlint_config = rule( + _ktlint_config_impl, + attrs = { + "editorconfig": attr.label( + doc = "Editor config file to use", + mandatory = False, + allow_single_file = True, + ), + }, +) diff --git a/kotlin/internal/lint/ktlint_fix.bzl b/kotlin/internal/lint/ktlint_fix.bzl new file mode 100644 index 000000000..d1e78ecb3 --- /dev/null +++ b/kotlin/internal/lint/ktlint_fix.bzl @@ -0,0 +1,135 @@ +load(":editorconfig.bzl", "get_editorconfig") +load(":ktlint_config.bzl", "KtlintConfigInfo") + +def _ktlint_fix_impl(ctx): + editorconfig = get_editorconfig(ctx.attr.config) + + editorconfig_arg = "--editorconfig={file}".format(file = editorconfig.path) if editorconfig else "" + + # Much of the following is lifted from: + # https://cs.opensource.google/bazel/bazel/+/refs/tags/4.0.0:src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt;l=114 + content = """#!/bin/bash +# Find our runfiles tree. +# +# Call this program X. X was generated by a rule. +# X may be invoked in many ways: +# 1a) directly by a user, with $0 in the output tree +# 1b) via 'bazel run' (similar to case 1a) +# 2) directly by a user, with $0 in X's runfiles tree +# 3) by another program Y which has a data dependency on X, with $0 in Y's runfiles tree +# 4) via 'bazel test' +# 5) by a genrule cmd, with $0 in the output tree +# 6) case 3 in the context of a genrule +# +# For case 1, $0 will be a regular file, and the runfiles tree will be +# at $0.runfiles. +# For case 2, $0 will be a symlink to the file seen in case 1. +# For case 3, we use Y's runfiles tree, which will be a superset of X's. +# For case 4, $JAVA_RUNFILES and $TEST_SRCDIR should already be set. +# Case 5 is handled like case 1. +# Case 6 is handled like case 3. + +self="$0" + +if [[ "$self" != /* ]]; then + self="$PWD/$self" +fi + +if [[ -z "$RUNFILES" ]]; then +while true; do + if [[ -e "$self.runfiles" ]]; then + RUNFILES="$self.runfiles" + break + fi + if [[ $self == *.runfiles/* ]]; then + RUNFILES="${{self%%.runfiles/*}}.runfiles" + break + fi + if [[ ! -L "$self" ]]; then + break + fi + readlink="$(readlink "$self")" + if [[ "$readlink" = /* ]]; then + self="$readlink" + else + # resolve relative symlink + self="${{self%%/*}}/$readlink" + fi +done +if [[ -z "$RUNFILES" ]]; then + echo 'Cannot locate runfiles directory.' + exit 1 +fi +fi + +# This is the end of the portion copied from the java_stub_template - what's below is original code. + +BUILD_DIR="$BUILD_WORKSPACE_DIRECTORY" +if [ -n "$BUILD_DIR" ]; then + BUILD_DIR="$BUILD_DIR/" +fi + +TOOL={executable} +if [[ ! -f "$TOOL" ]]; then + # The path to the tool contains starts with `/external` but when + # using --nolegacy_external_runfiles we need to strip that from + # the path. + TOOL="$RUNFILES/${{TOOL/#external\\///}}" + + if [[ ! -f "$TOOL" ]]; then + echo "Cannot locate linter. $TOOL" + exit 2 + fi +fi + +SRCS=({srcs}) +SRCS=${{SRCS[@]/#/$BUILD_DIR}} + +"$TOOL" --format {editorconfig_arg} $SRCS +""".format( + executable = ctx.executable._ktlint_tool.path, + editorconfig_arg = editorconfig_arg, + srcs = " ".join([src.path for src in ctx.files.srcs]), + ) + + content = ctx.expand_location(content, [ctx.attr._ktlint_tool]) + + executable = ctx.actions.declare_file("%s-lint-fix" % ctx.label.name) + ctx.actions.write( + output = executable, + content = content, + is_executable = True, + ) + runfiles = ctx.runfiles(files = [ctx.executable._ktlint_tool]) + + return [ + DefaultInfo( + executable = executable, + runfiles = runfiles, + ), + ] + +ktlint_fix = rule( + _ktlint_fix_impl, + attrs = { + "srcs": attr.label_list( + allow_files = [".kt", ".kts"], + doc = "Source files to review and fix", + mandatory = True, + allow_empty = False, + ), + "config": attr.label( + doc = "ktlint_config to use", + providers = [ + [KtlintConfigInfo], + ], + ), + "_ktlint_tool": attr.label( + default = "@com_github_pinterest_ktlint//file", + executable = True, + cfg = "target", + ), + }, + executable = True, + doc = "Lint Kotlin files and automatically fix them as needed", +) diff --git a/kotlin/internal/lint/ktlint_test.bzl b/kotlin/internal/lint/ktlint_test.bzl new file mode 100644 index 000000000..1e1e7d01e --- /dev/null +++ b/kotlin/internal/lint/ktlint_test.bzl @@ -0,0 +1,86 @@ +load(":editorconfig.bzl", "get_editorconfig") +load(":ktlint_config.bzl", "KtlintConfigInfo") + +def _ktlint(ctx, srcs, editorconfig): + """Generates a test action linting the input files. + + Args: + ctx: analysis context. + srcs: list of source files to be checked. + editorconfig: editorconfig file to use (optional) + + Returns: + A script running ktlint on the input files. + """ + java_runtime_info = ctx.attr._javabase[java_common.JavaRuntimeInfo] + args = [] + if editorconfig: + args.append("--editorconfig={file}".format(file = editorconfig.short_path)) + + for f in srcs: + args.append(f.path) + + return "PATH=\"{path}/bin:$PATH\" ; {linter} {args}".format( + path = java_runtime_info.java_home, + linter = ctx.executable._ktlint_tool.short_path, + args = " ".join(args), + ) + +def _ktlint_test_impl(ctx): + editorconfig = get_editorconfig(ctx.attr.config) + + script = _ktlint( + ctx, + srcs = ctx.files.srcs, + editorconfig = editorconfig, + ) + + ctx.actions.write( + output = ctx.outputs.executable, + content = script, + ) + + files = [ctx.executable._ktlint_tool] + ctx.files.srcs + if editorconfig: + files.append(editorconfig) + + return [ + DefaultInfo( + runfiles = ctx.runfiles( + files = files, + transitive_files = ctx.attr._javabase[java_common.JavaRuntimeInfo].files, + ).merge(ctx.attr._ktlint_tool[DefaultInfo].default_runfiles), + executable = ctx.outputs.executable, + ), + ] + +ktlint_test = rule( + _ktlint_test_impl, + attrs = { + "srcs": attr.label_list( + allow_files = [".kt", ".kts"], + doc = "Source files to lint", + mandatory = True, + allow_empty = False, + ), + "config": attr.label( + doc = "ktlint_config to use", + providers = [ + [KtlintConfigInfo], + ], + ), + "_ktlint_tool": attr.label( + default = "@com_github_pinterest_ktlint//file", + executable = True, + cfg = "host", + ), + "_javabase": attr.label( + default = "@bazel_tools//tools/jdk:current_java_runtime", + ), + }, + doc = "Lint Kotlin files, and fail if the linter raises errors.", + test = True, + toolchains = [ + "@bazel_tools//tools/jdk:toolchain_type", + ], +) diff --git a/kotlin/internal/repositories/release_repositories.bzl b/kotlin/internal/repositories/release_repositories.bzl index 81c23ff35..005b46af1 100644 --- a/kotlin/internal/repositories/release_repositories.bzl +++ b/kotlin/internal/repositories/release_repositories.bzl @@ -23,6 +23,7 @@ load( _http_archive = "http_archive", _http_file = "http_file", ) +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") load(":tools.bzl", "absolute_target") BAZEL_JAVA_LAUNCHER_VERSION = "3.7.0" @@ -61,6 +62,14 @@ def kotlin_repositories( sha256 = "a618e746e743f3119a9939e60645a02de40149aae9d63201c3cd05706010f6eb", ) + maybe( + _http_file, + name = "com_github_pinterest_ktlint", + sha256 = "4739662e9ac9a9894a1eb47844cbb5610971f15af332eac94d108d4f55ebc19e", + urls = ["https://github.com/pinterest/ktlint/releases/download/0.40.0/ktlint"], + executable = True, + ) + def _kotlin_compiler_impl(repository_ctx): """Creates the kotlinc repository.""" attr = repository_ctx.attr diff --git a/kotlin/kotlin.bzl b/kotlin/kotlin.bzl index a58101eb9..55cca336e 100644 --- a/kotlin/kotlin.bzl +++ b/kotlin/kotlin.bzl @@ -31,6 +31,9 @@ load( _kt_jvm_test = "kt_jvm_test", _kt_kotlinc_options = "kt_kotlinc_options", _kt_register_toolchains = "kt_register_toolchains", + _ktlint_config = "ktlint_config", + _ktlint_fix = "ktlint_fix", + _ktlint_test = "ktlint_test", ) kotlin_repositories = _kotlin_repositories @@ -47,3 +50,6 @@ kt_jvm_test = _kt_jvm_test kt_android_library = _kt_android_library kt_android_local_test = _kt_android_local_test kt_compiler_plugin = _kt_compiler_plugin +ktlint_config = _ktlint_config +ktlint_fix = _ktlint_fix +ktlint_test = _ktlint_test \ No newline at end of file diff --git a/kotlin/rules.bzl b/kotlin/rules.bzl index b3cead814..ab9f78407 100644 --- a/kotlin/rules.bzl +++ b/kotlin/rules.bzl @@ -42,6 +42,9 @@ load( _kt_js_import = "kt_js_import_macro", _kt_js_library = "kt_js_library_macro", ) +load("//kotlin/internal/lint:ktlint_config.bzl", _ktlint_config = "ktlint_config") +load("//kotlin/internal/lint:ktlint_fix.bzl", _ktlint_fix = "ktlint_fix") +load("//kotlin/internal/lint:ktlint_test.bzl", _ktlint_test = "ktlint_test") define_kt_toolchain = _define_kt_toolchain kt_kotlinc_options = _kt_kotlinc_options @@ -56,3 +59,6 @@ kt_jvm_test = _kt_jvm_test kt_android_library = _kt_android_library kt_android_local_test = _kt_android_local_test kt_compiler_plugin = _kt_compiler_plugin +ktlint_config = _ktlint_config +ktlint_fix = _ktlint_fix +ktlint_test = _ktlint_test \ No newline at end of file From 54894828695a50da9786dfb9126e4dda5a2828ff Mon Sep 17 00:00:00 2001 From: Simon Stewart Date: Fri, 12 Mar 2021 17:50:56 +0000 Subject: [PATCH 2/3] Improve documentation --- kotlin/internal/BUILD | 1 + kotlin/internal/lint/BUILD.bazel | 20 ++++++++++++++++++++ kotlin/internal/lint/ktlint_config.bzl | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/kotlin/internal/BUILD b/kotlin/internal/BUILD index 2ee549180..143ef4ce7 100644 --- a/kotlin/internal/BUILD +++ b/kotlin/internal/BUILD @@ -39,6 +39,7 @@ bzl_library( deps = [ "//kotlin/internal/js", "//kotlin/internal/jvm", + "//kotlin/internal/lint", "//kotlin/internal/utils", "//kotlin/internal/repositories", ], diff --git a/kotlin/internal/lint/BUILD.bazel b/kotlin/internal/lint/BUILD.bazel index e69de29bb..f7adc2445 100644 --- a/kotlin/internal/lint/BUILD.bazel +++ b/kotlin/internal/lint/BUILD.bazel @@ -0,0 +1,20 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "lint", + srcs = glob(["*.bzl"]), + visibility = ["//kotlin:__subpackages__"], +) diff --git a/kotlin/internal/lint/ktlint_config.bzl b/kotlin/internal/lint/ktlint_config.bzl index a0c2c02cd..59658ad4c 100644 --- a/kotlin/internal/lint/ktlint_config.bzl +++ b/kotlin/internal/lint/ktlint_config.bzl @@ -20,4 +20,8 @@ ktlint_config = rule( allow_single_file = True, ), }, + doc = """Used to configure ktlint. + + `ktlint` can be configured to use a `.editorconfig`, as documented at + https://github.com/pinterest/ktlint/#editorconfig""" ) From 47eb4c85d43c0627473286811f1bf3ec60a515e4 Mon Sep 17 00:00:00 2001 From: Simon Stewart Date: Fri, 12 Mar 2021 18:06:10 +0000 Subject: [PATCH 3/3] Add packaging rules --- kotlin/internal/BUILD | 1 + kotlin/internal/lint/BUILD.bazel | 9 +++++++++ kotlin/internal/lint/BUILD.release.bazel | 14 ++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 kotlin/internal/lint/BUILD.release.bazel diff --git a/kotlin/internal/BUILD b/kotlin/internal/BUILD index 143ef4ce7..10ba5ecc2 100644 --- a/kotlin/internal/BUILD +++ b/kotlin/internal/BUILD @@ -27,6 +27,7 @@ release_archive( deps = [ "//kotlin/internal/js:pkg", "//kotlin/internal/jvm:pkg", + "//kotlin/internal/lint:pkg", "//kotlin/internal/repositories:pkg", "//kotlin/internal/utils:pkg", ], diff --git a/kotlin/internal/lint/BUILD.bazel b/kotlin/internal/lint/BUILD.bazel index f7adc2445..6a1a29c7e 100644 --- a/kotlin/internal/lint/BUILD.bazel +++ b/kotlin/internal/lint/BUILD.bazel @@ -12,6 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("//kotlin/internal/utils:packager.bzl", "release_archive") + +release_archive( + name = "pkg", + srcs = glob(["*.bzl"]), + src_map = { + "BUILD.release.bazel": "BUILD.bazel", + }, +) bzl_library( name = "lint", diff --git a/kotlin/internal/lint/BUILD.release.bazel b/kotlin/internal/lint/BUILD.release.bazel new file mode 100644 index 000000000..9ed8bf448 --- /dev/null +++ b/kotlin/internal/lint/BUILD.release.bazel @@ -0,0 +1,14 @@ +# Copyright 2020 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +