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

Add Android Linting Example #3931

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
** xref:javalib/build-examples.adoc[]
** xref:javalib/web-examples.adoc[]
** xref:javalib/android-examples.adoc[]
** xref:javalib/android-linting.adoc[]
* xref:scalalib/intro.adoc[]
** xref:scalalib/builtin-commands.adoc[]
** xref:scalalib/module-config.adoc[]
Expand Down
14 changes: 14 additions & 0 deletions docs/modules/ROOT/pages/javalib/android-linting.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
= Linting Android Projects
:page-aliases: Linting_Android_Projects.adoc

include::partial$gtag-config.adoc[]

This page covers essential practices for maintaining and enforcing code quality
in Android projects using the Mill build tool. Proper linting helps detect
and resolve potential issues early, promoting better performance, security,
and user experience.


== Linting with Android cmdline lint tool

include::partial$example/javalib/android/2-linting.adoc[]
13 changes: 13 additions & 0 deletions example/javalib/android/2-linting/app/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.helloworld.app">
<uses-sdk android:minSdkVersion="9"/>
<uses-sdk android:targetSdkVersion="35"/>
<application android:label="Hello World" android:debuggable="true">
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.helloworld.app;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.view.ViewGroup.LayoutParams;
import android.view.Gravity;


public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Create a new TextView
TextView textView = new TextView(this);

// Set the text to "Hello, World!"
textView.setText("Hello, World!");

// Set text size
textView.setTextSize(32);

// Center the text within the view
textView.setGravity(Gravity.CENTER);

// Set layout parameters (width and height)
textView.setLayoutParams(new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));

// Set the content view to display the TextView
setContentView(textView);
}
}
29 changes: 29 additions & 0 deletions example/javalib/android/2-linting/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package build

import mill._
import mill.javalib.android.{AndroidSdkModule, AndroidLintModule}

// Create and configure an Android SDK module to manage Android SDK paths and tools.
object androidSdkModule0 extends AndroidSdkModule {
def buildToolsVersion = "35.0.0"
}

// Actual android application
object app extends AndroidLintModule {
def androidSdkModule = mill.define.ModuleRef(androidSdkModule0)
Copy link
Member

Choose a reason for hiding this comment

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

Let's include an example custom lint configuration here so people know what it looks like

}

/** See Also: app/AndroidManifest.xml */
/** See Also: app/src/main/java/com/helloworld/app/MainActivity.java */

/** Usage

> ./mill show app.androidLint # Display full path to the linting report in HTML
".../out/app/androidLint.dest/report.html"
Copy link
Member

@lihaoyi lihaoyi Nov 13, 2024

Choose a reason for hiding this comment

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

We should use cat on the HTML report to ensure certain lint errors get picked up and properly reported, and discuss how the code in MainActivity.java results in the lint warnings reported


*/

// This Mill build configuration includes a linting step, which is essential for ensuring code
// quality and adhering to best practices in Android development projects. Running the `androidLint` task
// produces a detailed HTML report identifying potential issues in the code, such as performance,
// security, and usability warnings. This helps maintain the health and quality of the codebase.
1 change: 1 addition & 0 deletions scalalib/src/mill/javalib/android/AndroidAppModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -254,4 +254,5 @@ trait AndroidAppModule extends JavaModule {

PathRef(keystoreFile)
}

}
82 changes: 82 additions & 0 deletions scalalib/src/mill/javalib/android/AndroidLintModule.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package mill.javalib.android

import mill._
import mill.api.PathRef
import mill.scalalib.JavaModule
import mill.javalib.android.AndroidSdkModule

/**
* Android Lint Module for integrating the Android Lint tool in a Mill build.
*
* This module provides configuration options for executing Android Lint, including setting paths,
* specifying lint rules, managing reports, and more.
*/
@mill.api.experimental
trait AndroidLintModule extends AndroidSdkModule with JavaModule {
Copy link
Member

@lihaoyi lihaoyi Nov 13, 2024

Choose a reason for hiding this comment

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

Why are we inheriting from both AndroidSdkModule and JavaModule? Those are typically not mixed in together, so there's something weird here. It should be on one or the other but not both, and from the usage example I'm guessing it should instead inherit from AndroidAppModule


/**
* Path to the project for which lint should run.
*/
def projectPath: T[PathRef] = Task.Source { millSourcePath }
Copy link
Member

Choose a reason for hiding this comment

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

Do we need this? Or can we just use millSourcePath everywhere instead?


/**
* Specifies the file format of lint report.
*/
def lintReportFormat: T[String] = Task { "html" }
Copy link
Member

Choose a reason for hiding this comment

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

The comment should list the possible formats we are aware of


/**
* Specifies the lint configuration XML file path. This allows setting custom lint rules or modifying existing ones.
*/
def lintConfigPath: T[Option[PathRef]] = Task { None }

/**
* Enable or disable warnings in the lint report.
*/
def warningsAsErrors: T[Boolean] = Task { false }

/**
* Additional options for lint (e.g., enabling/disabling specific checks).
*/
def lintOptions: T[Seq[String]] = Task { Seq("--check", "NewApi,InlinedApi") }
Copy link
Member

Choose a reason for hiding this comment

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

All tasks in this module should be prefixed by androidLint to make it clear where they come from and avoid collisions with tasks coming from other traits that get mixed in


/**
* Runs the Android Lint tool to generate a report on code quality issues.
*
* This method utilizes Android Lint, a tool provided by the Android SDK,
* to analyze the source code for potential bugs, performance issues, and
* best practices compliance. It generates an HTML report with the analysis
* results for review.
*
* The lint tool requires the Android SDK's command-line tools to be installed.
* The report is saved in the task's destination directory as "report.html".
*
* For more details on the Android Lint tool, refer to:
* [[https://developer.android.com/studio/write/lint]]
*/

def androidLint: T[PathRef] = Task {

val format = lintReportFormat()
val lintReport: os.Path = T.dest / s"report.$format"

// Map the report format to the corresponding lint command flag
val lintReportFlag = format match {
case "html" => "--html"
case "txt" => "--text"
case "xml" => "--xml"
case _ => throw new Exception(s"Unsupported report format: $format")
}

os.call(
Seq(
cmdlineToolsPath().path.toString + "/lint",
lintReportFlag,
lintReport.toString,
projectPath.toString
)
)

PathRef(lintReport)
}

}
8 changes: 8 additions & 0 deletions scalalib/src/mill/javalib/android/AndroidSdkModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ trait AndroidSdkModule extends Module {
PathRef(sdkPath().path / "build-tools" / buildToolsVersion())
}

/**
* Provides path to the Android cmdline tools for the selected version.
*/
def cmdlineToolsPath: T[PathRef] = Task {
installAndroidSdkComponents()
PathRef(sdkPath().path / "cmdline-tools" / "latest" / "bin")
}

/**
* Provides path to D8 Dex compiler, used for converting Java bytecode into Dalvik bytecode.
*/
Expand Down
Loading