Compiling LESS into CSS with Gradle without installing a plug-in - less

This may seem like an odd question. I want to compile LESS into CSS without installing a plug-in into my local gradle. There are several gradle plug-ins out there that compile LESS into CSS, which is great, but I would like to compile and use the plug-ins at runtime rather than install them prior.
Is this possible? If there was a standard plug-in like for CoffeeScript, it would be no big deal and easy, but there isn't one for LESS. I'm rather new to Gradle, so I'm unsure of how to move forward. The obvious solution is to bloody install the plug-in, but given a constraint that I can't, is it possible to compile/use it at runtime?
https://github.com/koenongena/lesscss-gradle-plugin
https://github.com/skhome/gradle-less-plugin
https://github.com/msgilligan/gradle-lesscss-plugin

In most cases you don't install plugins into your Gradle installation. Rather, you declare the usage of the plugin as part of your build.gradle. At build runtime, it then resolves any plugins that are needed for your build script, loads them, and then runs the build. The particular plugins you listed are a bit unusual in that (as far as I can tell) they haven't yet been published in a publicly accessible Maven repository (such as Maven Central or Bintray). After a quick search, I found one that appears to be in Maven Central:
https://github.com/obecker/gradle-lesscss-plugin
To use it, you don't need to install anything ahead of time. Instead, you declare it in your build.gradle like this:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'de.obqo.gradle:gradle-lesscss-plugin:1.0-1.3.3'
}
}
apply plugin: 'lesscss'
If you don't want to use a plugin, you can pretty easily compile LESS files in Gradle without using any plugin at all. That's because the Gradle plugin architecture builds on exactly the same DSL and deep API as you use in build scripts. The main difference is one of intent; a plugin has been packaged in a way that is intended for re-use in other projects.
The bare minimum needed is access to a library that performs LESS compilation, and a declaration of a task that uses it. Here's a quick example using lesscss-java. It only compiles a single LESS file, but it should be easy to extend for whatever your requirements are.
build.gradle:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.lesscss:lesscss:1.3.0"
}
}
task lessCompile << {
def compiler = new org.lesscss.LessCompiler()
compiler.compile(file("some.less"), file("some.css"))
}
some.less:
#mainColor: black;
body {
background-color: #mainColor;
}
After running gradle lessCompile, the result is some.css:
body {
background-color: #000000;
}

Related

How do I encapsulate version management for gradle plugins?

Problem
I have a setup of various distinct repos/projects (i.e. app1, app2, app3) that all depend on shared functionality in my base package.
The projects also use various other third-party dependencies (i.e. app1 and app3 use spring, all of them use kotlinx-serialization).
I want to synchronise the versions of all third-party dependencies, so that any project using my base package uses the same version of every third-party dependency. However, I don't want to introduce new dependencies to projects that do not use them (i.e. app2 does not use spring)
Solution attempts
For libraries, I have been able to solve this with the help of a gradle platform, which does exactly what I want - I specify the versions in my base package, then add the platform as a dependency to my projects and can then simply add dependencies by name (i.e. implementation("org.springframework.boot:some-package")) without having to specify a version number, because it uses the provided value from my platform.
However, for plugins, I have not been able to do this. Many libraries come with plugins and naturally the plugin should be at the same version as the library. I have tried various approaches, including writing a standalone plugin, but none have worked.
Current best idea
I added implementation("org.springframework.boot:spring-boot-gradle-plugin:3.0.2") to the dependencies of my standalone plugin. Then, I added the following code to my standalone plugin:
class BasePlugin : Plugin<Project> {
override fun apply(target: Project) {
target.plugins.apply("org.springframework.boot")
}
}
This works and applies the plugin to my main project at the correct version. However, there are 2 major problems with this:
a) Now every project applies the spring plugin, including app2 (which does not use spring).
b) I have many plugins to manage and no idea how to get the long implementation-string for most of them. I found the "org.springframework.boot:spring-boot-gradle-plugin:3.0.2" by looking up the plugin-id on https://plugins.gradle.org/ and then looking at the legacy plugin application section, which sounds like I am on the wrong track.
I just want to manage the versions of plugins and libraries of multiple projects/repos in a central place - this feels like a fairly basic use case - why is this so hard?
There are some great and detailed answers about dependency management, but unfortunately none worked to perform cross-project version management for plugins.
It seems that there is no gradle functionality to do this, but I got it working with a bit of a workaround. Here is my (working) approach, in hope that it helps someone else with this:
Create a Standalone gradle Plugin
In the build.gradle.kts of the plugin, include the maven coordinates (not its ID) of every other plugin whose version you want to manage in any of your projects in the dependency block with the api keyword. i.e. api("org.springframework:spring-web:6.0.2")
In the main projects, remove every other plugin from the plugins block, so that your custom standalone plugin is the only one remaining.
Create a file (i.e. a plugins.json or whatever you want) in the project root directory of all main projects and in there supply the plugin IDs of the plugins that you actually intend to use in that project. Just the IDs, no version numbers, i.e. "org.springframework.boot" for Spring's plugin. (Keep in mind that for plugins declared as kotlin("abc") you will have to add the prefix "org.jetbrains.com.", as the kotlin method is just syntactic sugar for that)
In your plugin source code, in the overriden apply method, look for. a file named plugins.json (or whatever you chose) in the project.buildFile.parent directory (which will be the directory of the project using this plugin, NOT of the plugin itself). From this file, read the plugin IDs
for every pluginID in the file, call project.plugins.apply(id)
How/Why it works:
The main project build.gradle.kts is executed, looks at the plugin block and applies your standalone plugin (which is the only one), which calls its apply method.
This plugin then applies other plugins based on their ID from the file.
Normally, this will throw an error because these plugins are not found, but because we defined them as dependencies with the api keyword in our standalone plugin, they are now available on the classpath and in exactly the version of that import statement.
Hope it helps someone!
I use version numbers in a gradle.properties file for this purpose. Since the introduction of Gradle version catalogs, my approach is probably a bit out of date, but I'll share it here anyway. It's based on the fact that plugin versions can be managed in settings.gradle.kts by reading values from the properties file.
In gradle.properties:
springBootVersion=3.0.2
In settings.gradle.kts:
pluginManagement {
val springBootVersion: String by settings
plugins {
id("org.springframework.boot") version springBootVersion
}
}
And finally in build.gradle.kts:
plugins {
id("org.springframework.boot")
}
dependencies {
val springBootVersion: String by project
implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion"))
}
Notice that the plugin version is omitted in the build script because it is already specified in the settings file.
And note also that the method for accessing the property in the settings script is slightly different from that in the build script.
a) Now every project applies the spring plugin, including app2 (which does not use spring).
It is indeed better to avoid applying too many plugins - and that's why Gradle encourages reacting to plugins.
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.*
import org.springframework.boot.gradle.plugin.SpringBootPlugin
class BasePlugin : Plugin<Project> {
override fun apply(target: Project) {
// don't apply
//target.plugins.apply("org.springframework.boot")
// instead, react!
target.plugins.withType<SpringBootPlugin>().configureEach {
// this configuration will only trigger if the project applies both
// BasePlugin *and* the Spring Boot pluging
}
// you can also react based on the plugin ID
target.pluginManager.withPlugin("org.springframework.boot") {
}
}
}
Using the class is convenient if you want to access the plugin, or the plugin's extension, in a typesafe manner.
You can find the Plugin's class by
looking in the source code for the class that implements Plugin<Project>,
in the plugin's build config for the implementationClass,
or in the published plugin JAR - in the META-INF/gradle-plugins directory there will be a file that has the implementationClass.
This doesn't help your version alignment problem - but I thought it was worth mentioning!
b) I have many plugins to manage and no idea how to get the long implementation-string for most of them. I found the "org.springframework.boot:spring-boot-gradle-plugin:3.0.2" by looking up the plugin-id on https://plugins.gradle.org/ and then looking at the legacy plugin application section, which sounds like I am on the wrong track.
You're on the right track with the "long implementation string" as you call it. I'll refer to those as the 'Maven coordinates' of the plugin.
Gradle Plugin Maven Coordinates
The plugin id of the Kotlin JVM plugin is org.jetbrains.kotlin.jvm, but the Maven coordinates are org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0 .
The 'legacy' part refers to how the plugins are applied, using the apply(plugin = "...") syntax. The new way uses the plugin {} block, but under the hood, both methods still use the Maven coordinates of the plugin.
If you add those Maven coordinates (with versions) to your Java Platform, then you can import the platform into your project. But where?
Defining plugin versions
There are a lot of ways to define plugins, so I'll only describe one, and coincidentally it will be compatible with defining the version using a Java Platform.
If you're familiar with buildSrc convention plugins, you'll know that they can apply plugins, but they can't define versions.
// ./buildSrc/src/main/kotlin/kotlin-jvm-convention.gradle.kts
plugins {
kotlin("jvm") version "1.8.0" // error: pre-compiled script plugins can't set plugin versions!
}
Instead, plugin versions must be defined in the build config for buildSrc
// ./buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
}
dependencies {
// the Maven coordinates of the Kotlin JVM plugin - including the version
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
}
This looks a lot more traditional, and so I hope the next step is clean: use your Java Platform!
Applying a Java Platform to buildSrc
// ./buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
}
dependencies {
// import your Java Platform
implementation(platform("my.group:my-platform:1.2.3"))
// no version necessary - it will be supplied by my.group:my-platform
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin")
}
Note that this same method will also apply if your projects an 'included build' instead of buildSrc.
Once the plugin versions are defined in ./buildSrc/build.gradle.kts, you can use them throughout your project (whether in convention plugins, or in subprojects), they will be aligned.
// ./subproject-alpha/build.gradle.kts
plugins {
kotlin("jvm") // no version here - it's defined in buildSrc/build.gradle.kts
}

The same Gradle Kotlin `copy.from` api use `it` when called from build file (kt) and `this` when called from Pluign (kt)

I hope someone can explain this to me.
I was moving some code from a Gradle build file into a Gradle plugin.
Below are two code snippes calling the same from function (based on Ideas indexing)
I noticed that there are some strange differences between how the apis can be used in those two contexts.
I know that Gradle is adding some extra syntax suger around the build files which is why I need to manually cast the task in the Plugin.kt file, but I cannot find anything that explains why from in context of the Build file has this as context where in the plugin the function uses it to access the into function.
It is not just Idea that reports this, running Gradle also shows that it must be like this.
I assume this is something special to Kotlin's way of handling the Action interface in different contexts:
kts file (No wrapping class)
kt file (with class)
Here are the two samples
hostedStaticFiles is gradle configuration that will be used to configure web frontend from a separate build.
build.gradle.kts
tasks.getByName<ProcessResources>("processResources") {
this.from(hostedStaticFiles) {
this#from.into("static") // <-- Note use of this here
}
}
Plugin.kt
project.tasks.getByName("processResources").let<Task, ProcessResources> {
if (it !is ProcessResources) {
throw IllegalStateException("The processResources task in Project is not of type ${ProcessResources::class.java}")
}
it
}.apply {
dependsOn(hostedStaticFiles)
this#apply.from(hostedStaticFiles) { it -> // <-- Note use of it here and below
it.into("static")
}
}
dependencies {
hostedStaticFiles(project("client"))
}
I hobe someone can point me to an explanation or preferably documentation on why this behaves this way :)
Gradle version 7.4.1
###################
After getting the answer from #Joffrey I updated my buildSrc/build.gradle.kts with the below plugin configuration and it all started working as expected.
plugins {
`java-gradle-plugin`
`kotlin-dsl`
}
Gradle uses the HasImplicitReceiver annotation on some function types (like Action), so you can use this instead of it. It leverages Kotlin's SAM-with-receiver compiler plugin.
In Kotlin build scripts (.gradle.kts files) you benefit from this automatically because the Kotlin compiler used to compile your scripts is already properly configured. However, in custom plugin projects, you are in control of the build and you need to apply the kotlin-dsl plugin yourself. As mentioned in the documentation, it does a few things for you, including:
Configures the Kotlin compiler with the same settings that are used for Kotlin DSL scripts, ensuring consistency between your build logic and those scripts.

Multi-project Gradle+Kotlin: How to create Jar containing all sub-projects using Kotlin DSL?

I have a Gradle project with two subprojects. The parent does not contain any code; all the Kotlin code is in the two subprojects. All Gradle build files are defined in the Kotlin DSL.
Upon building, Gradle generates two JAR files, one in the build subfolder of each subproject. I believe this is the intended default behavior of Gradle. But this is not what I want.
I want to publish the JAR file of the parent project as a Maven artifact. Therefore, I need both subprojects to be included in one JAR file. How can I achieve this?
Note: On this web page, the author seems to achieve pretty much what I would need in this code snippet:
apply plugin: "java"
subprojects.each { subproject -> evaluationDependsOn(subproject.path)}
task allJar(type: Jar, dependsOn: subprojects.jar) {
baseName = 'multiproject-test'
subprojects.each { subproject ->
from subproject.configurations.archives.allArtifacts.files.collect {
zipTree(it)
}
}
}
artifacts {
archives allJar
}
However, this is defined in Gradle's native Groovy DSL. And I find myself unable to translate it into the Kotlin DSL. I tried to put a Groovy build file (*.gradle) besides the Kotlin build file (*.gradle.kts), but this led to a strange build error. I'm not sure if mixed build file languages are supported. Besides, I would consider it bad practice too. Better only define all build files in just one language.
Also, the example above pertains to the Java programming language. But I do not expect this to be a big problem, as both Java and Kotlin produce JVM bytecode as compile output.
More clarification:
I am not talking about a "fat JAR". Dependencies and the Kotlin library are not supposed to be included in the JAR.
I do not care if the JAR files for the subprojects are still getting built or not. I'm only interested in the integrated JAR that contains both subprojects.
The main point is getting the combined JAR for the binaries. Combined JARs for the sources and JavaDoc would be a nice-to-have, but are not strictly required.
I would use the Gradle guide Creating "uber" or "fat" JARs from the Gradle documentation as a basis. What you want is essentially the same thing. It's also much better than the Groovy example you found, as it doesn't use the discouraged subprojects util, or 'simple sharing' that requires knowing how the other projects are configured.
Create a configuration for resolving other projects.
// build.gradle.kts
val mergedJar by configurations.creating<Configuration> {
// we're going to resolve this config here, in this project
isCanBeResolved = true
// this configuration will not be consumed by other projects
isCanBeConsumed = false
// don't make this visible to other projects
isVisible = false
}
Use the new configuration to add dependencies on the projects we want to add into our combined Jar
dependencies {
mergedJar(project(":my-subproject-alpha"))
mergedJar(project(":my-subproject-beta"))
}
Now copy the guide from the docs, except instead of using configurations.runtimeClasspath we can use the mergedJar configuration, which will only create the subprojects we specified.
However we need to make some modifications.
I've adjusted the example to edit the existing Jar task rather than creating a new 'fatJar' task.
for some reason, setting isTransitive = false causes Gradle to fail resolution. Instead I've added a filter (it.path.contains(rootDir.path)) to make sure the Jars we're consuming are inside the project.
tasks.jar {
dependsOn(mergedJar)
from({
mergedJar
.filter {
it.name.endsWith("jar") && it.path.contains(rootDir.path)
}
.map {
logger.lifecycle("depending on $it")
zipTree(it)
}
})
}

Can't add kotlinx to a fresh Kotlin/JS project

Trying to figure out what I'm doing wrong. I created a small video of exactly what I'm doing in IntelliJ on Windows.
https://www.youtube.com/watch?v=nIH_55Zbxus&feature=youtu.be
And I'll describe it here in text.
Create a new project
Tick the Gradle > Kotlin/JS for browser template and untick everything else
Add implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8' to the dependencies block in the build.gradle.
Sync the gradle files
Attempt to use something from the kotlinx.coroutines namespace
Hopefully its just a silly thing I'm missing. I expected to just have to add the coroutines library to be able to actually import it. It looks like the library is listed in the project structure for the main module so I'm not sure what else might be wrong. Here is a repo of the project too.
plugins {
id 'org.jetbrains.kotlin.js' version '1.3.72'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-js"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
testImplementation "org.jetbrains.kotlin:kotlin-test-js"
}
kotlin.target.browser { }
You should add kotlinx-coroutines-core-js dependency. See the documentation: https://github.com/Kotlin/kotlinx.coroutines#js

IntelliJ, Android and Gradle

I am trying to get my existing Android Project working with gradle and IntelliJ 12.
Previously I had it working with maven but that didn't seem to be so flexible as gradle, and from what I think I got to know is that I need less subfolders.
My Android project is divided into a clean java library (:core) and the actual Application (:android). These two projects are both in my main project folder.
~-+MainProject
|--+core
| L--build.gradle
|--+android
| L--build.gradle
|--build.gradle
L--settings.gradle
I think the ideal solution to get gradle work with this is to treat the (:core) and (:android) project as nested projects, meaning I can simply be with my cmd in MainProject source folder to start the gradle tasks.
However I came up with different problems:
Gradle dependencies are only included in the core project
that means neither in the instrumentatetionTest nor in the main project files are any
directories correctly set as source / test directories
IntelliJ doesn't resolve any classes that I added as dependencies in gradle (AndroidSDK, junit, mockito, those from the :core project)
tried to use the plugin 'idea' but either I used it wrong or it didn't solve the problem
I get duplicate dependency Error for building the APK
must have something to do with junit:4.11 and mockito-core:1.9.5 that I added
what I tried:
deleting the dependencys -> build fails cause some classes of course could'nt be resolved
changing to junit:4.5+ as suggesed in some other thread -> no change at all
Here are the *.gradle configuartions
MainProject:
-- settings.gradle
include ':core', ':android'
-- build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.6.+'
}
}
subprojects {
repositories {
mavenLocal()
maven { url "http://repo.maven.apache.org/maven2" }
}
}
:core
-- build.gradle
apply plugin: 'java'
dependencies {
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.9.5'
}
:android
apply plugin: 'android'
repositories {
mavenCentral()
}
dependencies {
compile project(":core")
compile 'com.google.android:android:4.1.1.4'
instrumentTestCompile 'junit:junit:4.11'
instrumentTestCompile 'org.mockito:mockito-core:1.9.5'
instrumentTestCompile 'com.google.dexmaker:dexmaker:1.0'
instrumentTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
instrumentTestCompile 'com.jayway.android.robotium:robotium-solo:3.6'
}
/* ... androidSettings
I hope someone could help me with that
MFG Dornathal
Alright, so you've got the right idea, but there are a few changes you still need.
Your root build.gradle file should be as follows:
subprojects {
repositories {
mavenCentral()
}
}
You only need to include mavenLocal() if you are using a locally installed repo. Most people don't, and nothing in your project indicates that you need one.
mavenCentral() can be used to replace to maven URL you were using.
We only need to modify the buildscript for the android project, so we should localize that to the android project's build.gradle.
Your settings.gradle and your build.gradle for the core project are good.
Your build.gradle for the android project however, needs some changes:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.6.+'
}
}
apply plugin: 'android'
dependencies {
compile project(":core")
instrumentTestCompile 'com.jayway.android.robotium:robotium-solo:3.6'
}
By including the android plugin, we link directly to the installed SDK. This means we no longer need to include the android dependency: compile 'com.google.android:android:4.1.1.4'.
We don't need to include junit. It's provided by the SDK, so we'll just use that. One thing to note is that the SDK only includes JUnit 3.
We don't need to include mockito and dexmaker unless we actually use it for the android tests. If it's only being used for the tests on the java library, we don't need it here.
To answer your questions:
I'm not sure what you're asking here. It might help if you should your project structure including the directories in which you have files and such.
Your hunch is correct. Hamcrest, which provides matchers for tests, made a breaking change to their API between versions 1.1 and 1.3. JUnit 4.11 has a dependency on Hamcrest 1.3. Mockito 1.9.5 however, depends on Hamcrest 1.1. So when both are included, the 1.3 overrides the 1.1 & Mockito no longer works. Reverting to JUnit 4.5 is also a problem. Junit 4.5 includes the Hamcrest 1.1 jar as a file rather than as a dependency in the POM. So this will cause issues when we have 2 versions of the same file. JUnit 4.10 is the way you want to go here. This has the dependency on Hamcrest 1.1 and includes it in the POM instead of as a file. Win win! I've had issues with this too, and the best way for me to figure it out was to just look at the POM files on Maven Central and see what they tell me.
One final note. Intellij 12 cannot handle Gradle Multi-project builds. You need to switch to Android Studio or Intellij 13 for that.