Horm Codes

Talking About Nx Gradle at JavaZone Conference

Check out my talk at JavaZone 2024 with title Using NX for Monorepo with Gradle Services:

You can also access slides directly at slides.horm.codes/javazone-2024. Fun fact, the slide repository uses Nx by itself (see here for more details).

Resources

Here are some code snippets that can be useful when using Nx with Gradle…

Continuous Integration in GitHub Actions:

Workflow example:

name: CI

on:
  pull_request:

env:
  JDK_VERSION: 19
  JDK_DISTRIBUTION: temurin
  NODE_VERSION: 20

jobs:
  lint:
    name: Lint Code
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: ${{ env.JDK_DISTRIBUTION }}
          java-version: ${{ env.JDK_VERSION }}

      - name: Setup Gradle Cache
        uses: gradle/actions/setup-gradle@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'yarn'

      - run: yarn install --frozen-lockfile --prefer-offline

      - run: yarn nx affected -t lint --base=origin/main --parallel=3

  unit_tests:
    runs-on: ubuntu-latest
    name: Run unit tests
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: ${{ env.JDK_DISTRIBUTION }}
          java-version: ${{ env.JDK_VERSION }}

      - name: Setup Gradle Cache
        uses: gradle/actions/setup-gradle@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'yarn'

      - run: yarn install --frozen-lockfile --prefer-offline

      - run: yarn nx affected -t test --base=origin/main

Continuous Deployment in GitHub Actions:

Workflow example:

name: CD

on:
  push:
    branches:
      - main

env:
  JDK_VERSION: 19
  JDK_DISTRIBUTION: corretto
  NODE_VERSION: 20


jobs:
  deploy:
    runs-on: ubuntu-latest
    name: Deploy affected services to dev
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: ${{ env.JDK_DISTRIBUTION }}
          java-version: ${{ env.JDK_VERSION }}

      - name: Setup Gradle Cache
        uses: gradle/actions/setup-gradle@v3

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'yarn'

      - run: yarn install --frozen-lockfile --prefer-offline

      - name: Find last successful workflow run
        uses: nrwl/nx-set-shas@v4

      - run: yarn nx affected -t deploy

Publishing Libraries to GitHub Maven Registry

Nx config:

{
  // ... 
  "release": {
    "projects": [
      "lib",
      "api-client",
      "api-rest-models"
    ],
    "projectsRelationship": "fixed",
    "version": {
      "conventionalCommits": true,
      "git": {
        "commit": false
      }
    },
    "changelog": {
      "git": {
        "commit": false
      },
      "workspaceChangelog": {
        "createRelease": "github"
      }
    }
  }
}

Release script:

const {
  releaseChangelog,
  releasePublish,
  releaseVersion
} = require("nx/release");
const fs = require("fs");
const yargs = require('yargs');

const gradlePropsPath = "./gradle.properties";

(async () => {
  const options = await yargs
    .version(false) // don't use the default meaning of version in yargs
    .option('dryRun', {
      alias: 'd',
      description:
        'Whether or not to perform a dry-run of the release process, defaults to false',
      type: 'boolean',
      default: false,
    })
    .parseAsync();


  const {
    workspaceVersion,
    projectsVersionData
  } = await releaseVersion({ dryRun: options.dryRun });

  if (typeof workspaceVersion !== "string") {
    console.warn("No new version detected. Exiting...");
    process.exit(0);
    return;
  }

  await updateGradleVersion(`v${workspaceVersion}`);
  await releaseChangelog({
    versionData: projectsVersionData,
    version: workspaceVersion,
    dryRun: options.dryRun,
  });

  if (options.dryRun) {
    console.warn("Dry-run mode enabled. Exiting...");
    process.exit(0);
    return
  }

  const publishStatus = await releasePublish({ dryRun: options.dryRun });

  process.exit(publishStatus);
})();


async function updateGradleVersion(newVersion) {
  try {
    fs.writeFileSync(gradlePropsPath, `version=${newVersion}`, "utf8");
    console.log(`gradle.properties file created with version ${newVersion}`);
  } catch (err) {
    console.error("Error updating gradle.properties file:", err);
  }
}

Workflow example:

name: CD

on:
  push:
    branches:
      - main

env:
  JDK_VERSION: 19
  JDK_DISTRIBUTION: corretto
  NODE_VERSION: 20

jobs:
  publish_libs:
    runs-on: ubuntu-latest
    name: Publish libraries
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: ${{ env.JDK_DISTRIBUTION }}
          java-version: ${{ env.JDK_VERSION }}

      - name: Setup Gradle Cache
        uses: gradle/actions/setup-gradle@v3

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'yarn'

      - run: yarn install --frozen-lockfile --prefer-offline

      - name: Define Git user
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com

      - run: node ./tools/scripts/release.js
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_ACTOR: ${{ github.actor }}