diff --git a/build.gradle b/build.gradle index d773cddb68a2..47597b192c47 100644 --- a/build.gradle +++ b/build.gradle @@ -105,6 +105,7 @@ buildscript { classpath 'com.android.tools.build:gradle:4.2.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2' classpath 'org.apache.commons:commons-exec:1.3' + classpath 'com.diffplug.spotless:spotless-plugin-gradle:5.16.0' classpath 'org.tomlj:tomlj:1.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } @@ -268,3 +269,17 @@ idea { } } } + +subprojects { + apply plugin: "com.diffplug.spotless" + + spotless { + java { + target project.fileTree(project.projectDir) { + include '**/*.java' + exclude '**/thirdparty/**' + } + googleJavaFormat('1.7') + } + } +} diff --git a/docs/code-quality/lint/linters/android-format.rst b/docs/code-quality/lint/linters/android-format.rst new file mode 100644 index 000000000000..b5d6509bba37 --- /dev/null +++ b/docs/code-quality/lint/linters/android-format.rst @@ -0,0 +1,28 @@ +Spotless +======== + +`Spotless `__ is a pluggable formatter +for Gradle and Android. + +In our current configuration, Spotless includes the +`Google Java Format plug-in https://github.com/google/google-java-format`__ +which formats all our Java code using the Google Java coding style guidelines. + + +Run Locally +----------- + +The mozlint integration of spotless can be run using mach: + +.. parsed-literal:: + + $ mach lint --linter android-format + +Alternatively, omit the ``--linter android-format`` and run all configured linters, which will include +spotless. + + +Autofix +------- + +The spotless linter provides a ``--fix`` option. diff --git a/mobile/android/geckoview/checkstyle.xml b/mobile/android/geckoview/checkstyle.xml index 1bd280e8cab1..d858bf090d86 100644 --- a/mobile/android/geckoview/checkstyle.xml +++ b/mobile/android/geckoview/checkstyle.xml @@ -8,18 +8,6 @@ - - - - - - - - - - - - @@ -35,9 +23,6 @@ - - - @@ -64,19 +49,10 @@ - - - - - - - - - diff --git a/mobile/android/gradle.configure b/mobile/android/gradle.configure index 6f24ee4b2528..be236802a29d 100644 --- a/mobile/android/gradle.configure +++ b/mobile/android/gradle.configure @@ -213,6 +213,30 @@ def gradle_android_api_lint_tasks(build_config): set_config("GRADLE_ANDROID_API_LINT_TASKS", gradle_android_api_lint_tasks) +set_config("GRADLE_ANDROID_FORMAT_LINT_FIX_TASKS", ["spotlessJavaApply"]) + + +@dependable +def gradle_android_format_lint_check_tasks(): + return ["spotlessJavaCheck"] + + +set_config( + "GRADLE_ANDROID_FORMAT_LINT_CHECK_TASKS", gradle_android_format_lint_check_tasks +) + +set_config( + "GRADLE_ANDROID_FORMAT_LINT_FOLDERS", + [ + "mobile/android/annotations", + "mobile/android/geckoview", + "mobile/android/geckoview_example", + "mobile/android/examples/messaging_example", + "mobile/android/examples/port_messaging_example", + ], +) + + @depends(gradle_android_build_config) def gradle_android_checkstyle_tasks(build_config): """Gradle tasks run by |mach android checkstyle|.""" @@ -400,6 +424,7 @@ def gradle_android_dependencies(): @depends( gradle_android_api_lint_tasks, + gradle_android_format_lint_check_tasks, gradle_android_checkstyle_tasks, gradle_android_dependencies, ) diff --git a/taskcluster/ci/source-test/mozlint-android.yml b/taskcluster/ci/source-test/mozlint-android.yml index 73fc015a8dee..309830dd97bc 100644 --- a/taskcluster/ci/source-test/mozlint-android.yml +++ b/taskcluster/ci/source-test/mozlint-android.yml @@ -70,6 +70,7 @@ lints: ./mach --log-no-times build pre-export export && ./mach --log-no-times lint -f treeherder -f json:/builds/worker/mozlint.json --linter android-api-lint + --linter android-format --linter android-javadoc --linter android-checkstyle --linter android-lint diff --git a/tools/lint/android-format.yml b/tools/lint/android-format.yml new file mode 100644 index 000000000000..b13521a0194b --- /dev/null +++ b/tools/lint/android-format.yml @@ -0,0 +1,15 @@ +--- +android-format: + description: Android formatting lint + include: ['mobile/android'] + exclude: [] + extensions: ['java'] + support-files: + - 'mobile/android/**/Makefile.in' + - 'mobile/android/config/**' + - 'mobile/android/gradle.configure' + - 'mobile/android/**/moz.build' + - '**/*.gradle' + type: global + payload: android.lints:format + setup: android.lints:setup diff --git a/tools/lint/android/lints.py b/tools/lint/android/lints.py index 617df22e7829..2a9e6f3cbd0e 100644 --- a/tools/lint/android/lints.py +++ b/tools/lint/android/lints.py @@ -6,6 +6,7 @@ import itertools import json +import glob import os import re import six @@ -18,7 +19,6 @@ from mozpack.files import FileFinder import mozpack.path as mozpath from mozlint import result - # The Gradle target invocations are serialized with a simple locking file scheme. It's fine for # them to take a while, since the first will compile all the Java, etc, and then perform # potentially expensive static analyses. @@ -84,6 +84,45 @@ def gradle(log, topsrcdir=None, topobjdir=None, tasks=[], extra_args=[], verbose raise +def format(config, fix=None, **lintargs): + topsrcdir = lintargs["root"] + topobjdir = lintargs["topobjdir"] + + if fix: + tasks = lintargs["substs"]["GRADLE_ANDROID_FORMAT_LINT_FIX_TASKS"] + else: + tasks = lintargs["substs"]["GRADLE_ANDROID_FORMAT_LINT_CHECK_TASKS"] + + gradle( + lintargs["log"], + topsrcdir=topsrcdir, + topobjdir=topobjdir, + tasks=tasks, + extra_args=lintargs.get("extra_args") or [], + ) + + results = [] + for path in lintargs["substs"]["GRADLE_ANDROID_FORMAT_LINT_FOLDERS"]: + folder = os.path.join( + topobjdir, "gradle", "build", path, "spotless", "spotlessJava" + ) + for filename in glob.iglob(folder + "/**/*.java", recursive=True): + err = { + "rule": "spotless-java", + "path": os.path.join(path, mozpath.relpath(filename, folder)), + "lineno": 0, + "column": 0, + "message": "Formatting error, please run ./mach lint -l android-format --fix", + "level": "error", + } + results.append(result.from_config(config, **err)) + + # If --fix was passed, we just report the number of files that were changed + if fix: + return {"results": [], "fixed": len(results)} + return results + + def api_lint(config, **lintargs): topsrcdir = lintargs["root"] topobjdir = lintargs["topobjdir"] diff --git a/tools/lint/mach_commands.py b/tools/lint/mach_commands.py index 282ccdd2069f..6ebfaa3cb676 100644 --- a/tools/lint/mach_commands.py +++ b/tools/lint/mach_commands.py @@ -8,6 +8,7 @@ import os from mozbuild.base import ( BuildEnvironmentNotFoundException, + MachCommandConditions as conditions, ) @@ -31,6 +32,7 @@ if os.path.exists(thunderbird_excludes): GLOBAL_EXCLUDES = ["node_modules", "tools/lint/test/files", ".hg", ".git"] VALID_FORMATTERS = {"black", "clang-format", "rustfmt"} +VALID_ANDROID_FORMATTERS = {"android-format"} def setup_argument_parser(): @@ -143,16 +145,20 @@ def eslint(command_context, paths, extra_args=[], **kwargs): def format_files(command_context, paths, extra_args=[], **kwargs): linters = kwargs["linters"] + formatters = VALID_FORMATTERS + if conditions.is_android(command_context): + formatters |= VALID_ANDROID_FORMATTERS + if not linters: - linters = VALID_FORMATTERS + linters = formatters else: - invalid_linters = set(linters) - VALID_FORMATTERS + invalid_linters = set(linters) - formatters if invalid_linters: print( "error: One or more linters passed are not valid formatters. " "Note that only the following linters are valid formatters:" ) - print("\n".join(sorted(VALID_FORMATTERS))) + print("\n".join(sorted(formatters))) return 1 kwargs["linters"] = list(linters)