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

Introduce the dependency caching for Maven and Gradle #193

Merged
merged 26 commits into from Aug 19, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fef7d58
implement a core logic to cache dependnecies
KengoTODA Jul 13, 2021
4a7bf99
integrate the cache logic to entry points
KengoTODA Jul 13, 2021
945940e
add a user doc about the dependency cache feature
KengoTODA Jul 13, 2021
0591f86
reflect changes to the dist dir
KengoTODA Jul 13, 2021
f537562
add a prefix to the cache key
KengoTODA Jul 14, 2021
b047f97
test: extract build.gradle to a file in __tests__ dir
KengoTODA Jul 14, 2021
3e2fde2
run the restore e2e test on the specified OS
KengoTODA Jul 14, 2021
a5a0c52
add an e2e test for maven
KengoTODA Jul 14, 2021
1872d8e
fix the dependency among workflows
KengoTODA Jul 14, 2021
313e1ad
stabilize the cache on the Windows in e2e test
KengoTODA Jul 15, 2021
8687e45
add .gitignore files to __tests__/cache directories
KengoTODA Jul 15, 2021
6977c03
try to run restore after the authentication
KengoTODA Jul 14, 2021
7fe6c4d
use the key in state to save caches in the post process
KengoTODA Jul 15, 2021
fae2927
suggest users to run without daemon if fail to save Gradle cache on W…
KengoTODA Jul 20, 2021
f6a3b97
add missing description in the README.md
KengoTODA Jul 20, 2021
5f3f74c
run clean-up tasks in serial
KengoTODA Aug 2, 2021
53f73ba
Add validation for post step (#3)
dmitry-shibanov Aug 18, 2021
862dedb
Merge remote-tracking branch 'origin/main' into introduce-cache
KengoTODA Aug 18, 2021
098a656
Update src/cleanup-java.ts
KengoTODA Aug 19, 2021
cb966d0
Update src/cache.ts
KengoTODA Aug 19, 2021
6edf849
style: put the name of input to the constants.ts
KengoTODA Aug 19, 2021
0082baa
format: run `npm run build` to reflect changes to the dist dir
KengoTODA Aug 19, 2021
ea721b3
chore: update licensed files by `licensed cache`
KengoTODA Aug 19, 2021
807e574
fix: rerun ncc on macOS with node v12
KengoTODA Aug 19, 2021
33dfe17
build: follow the suggestion at PR page
KengoTODA Aug 19, 2021
67b74a1
fix: throw error in case of no package manager file found
KengoTODA Aug 19, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
113 changes: 113 additions & 0 deletions .github/workflows/e2e-cache.yml
@@ -0,0 +1,113 @@
name: Validate cache
on:
push:
KengoTODA marked this conversation as resolved.
Show resolved Hide resolved
branches:
- main
- releases/*
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'

defaults:
run:
shell: bash

jobs:
gradle-save:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Run setup-java with the cache for gradle
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: gradle
- name: Create files to cache
# Need to avoid using Gradle daemon to stabilize the save process on Windows
# https://github.com/actions/cache/issues/454#issuecomment-840493935
run: |
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle
if [ ! -d ~/.gradle/caches ]; then
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
exit 1
fi
gradle-restore:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
needs: gradle-save
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Run setup-java with the cache for gradle
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: gradle
- name: Confirm that ~/.gradle/caches directory has been made
run: |
if [ ! -d ~/.gradle/caches ]; then
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
exit 1
fi
ls ~/.gradle/caches/
maven-save:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Run setup-java with the cache for maven
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: maven
- name: Create files to cache
run: |
mvn verify -f __tests__/cache/maven/pom.xml
if [ ! -d ~/.m2/repository ]; then
echo "::error::The ~/.m2/repository directory does not exist unexpectedly"
exit 1
fi
maven-restore:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
needs: maven-save
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Run setup-java with the cache for maven
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: maven
- name: Confirm that ~/.m2/repository directory has been made
run: |
if [ ! -d ~/.m2/repository ]; then
echo "::error::The ~/.m2/repository directory does not exist unexpectedly"
exit 1
fi
ls ~/.m2/repository
13 changes: 13 additions & 0 deletions README.md
Expand Up @@ -58,6 +58,19 @@ Currently, the following distributions are supported:

**NOTE:** The different distributors can provide discrepant list of available versions / supported configurations. Please refer to the official documentation to see the list of supported versions.

#### Supported cache types
Currently, `gradle` and `maven` are supported. You can set `cache` input like below:
```yaml
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
cache: 'gradle' # will restore cache of dependencies and wrappers
- run: ./gradlew build
```

### Check latest
In the basic examples above, the `check-latest` flag defaults to `false`. When set to `false`, the action tries to first resolve a version of Java from the local tool cache on the runner. If unable to find a specific version in the cache, the action will download a version of Java. Use the default or set `check-latest` to `false` if you prefer a faster more consistent setup experience that prioritizes trying to use the cached versions at the expense of newer versions sometimes being available for download.

Expand Down
181 changes: 181 additions & 0 deletions __tests__/cache.test.ts
@@ -0,0 +1,181 @@
import { mkdtempSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { restore, save } from '../src/cache';
import * as fs from 'fs';
import * as os from 'os';
import * as core from '@actions/core';
import * as cache from '@actions/cache';

describe('dependency cache', () => {
const ORIGINAL_RUNNER_OS = process.env['RUNNER_OS'];
const ORIGINAL_GITHUB_WORKSPACE = process.env['GITHUB_WORKSPACE'];
const ORIGINAL_CWD = process.cwd();
let workspace: string;
let spyInfo: jest.SpyInstance<void, Parameters<typeof core.info>>;
let spyWarning: jest.SpyInstance<void, Parameters<typeof core.warning>>;

beforeEach(() => {
workspace = mkdtempSync(join(tmpdir(), 'setup-java-cache-'));
switch (os.platform()) {
case 'darwin':
process.env['RUNNER_OS'] = 'macOS';
break;
case 'win32':
process.env['RUNNER_OS'] = 'Windows';
break;
case 'linux':
process.env['RUNNER_OS'] = 'Linux';
break;
default:
throw new Error(`unknown platform: ${os.platform()}`);
}
process.chdir(workspace);
// This hack is necessary because @actions/glob ignores files not in the GITHUB_WORKSPACE
// https://git.io/Jcxig
process.env['GITHUB_WORKSPACE'] = projectRoot(workspace);
});

beforeEach(() => {
spyInfo = jest.spyOn(core, 'info');
spyWarning = jest.spyOn(core, 'warning');
});

afterEach(() => {
process.chdir(ORIGINAL_CWD);
process.env['GITHUB_WORKSPACE'] = ORIGINAL_GITHUB_WORKSPACE;
process.env['RUNNER_OS'] = ORIGINAL_RUNNER_OS;
});

describe('restore', () => {
let spyCacheRestore: jest.SpyInstance<
ReturnType<typeof cache.restoreCache>,
Parameters<typeof cache.restoreCache>
>;

beforeEach(() => {
spyCacheRestore = jest
.spyOn(cache, 'restoreCache')
.mockImplementation((paths: string[], primaryKey: string) => Promise.resolve(undefined));
});

it('throws error if unsupported package manager specified', () => {
return expect(restore('ant')).rejects.toThrowError('unknown package manager specified: ant');
});

describe('for maven', () => {
it('warns if no pom.xml found', async () => {
await restore('maven');
expect(spyWarning).toBeCalledWith(
`No file in ${projectRoot(
workspace
)} matched to [**/pom.xml], make sure you have checked out the target repository`
);
});
it('downloads cache', async () => {
createFile(join(workspace, 'pom.xml'));

await restore('maven');
expect(spyCacheRestore).toBeCalled();
expect(spyWarning).not.toBeCalled();
expect(spyInfo).toBeCalledWith('maven cache is not found');
});
});
describe('for gradle', () => {
it('warns if no build.gradle found', async () => {
await restore('gradle');
expect(spyWarning).toBeCalledWith(
`No file in ${projectRoot(
workspace
)} matched to [**/*.gradle*,**/gradle-wrapper.properties], make sure you have checked out the target repository`
);
});
it('downloads cache based on build.gradle', async () => {
createFile(join(workspace, 'build.gradle'));

await restore('gradle');
expect(spyCacheRestore).toBeCalled();
expect(spyWarning).not.toBeCalled();
expect(spyInfo).toBeCalledWith('gradle cache is not found');
});
it('downloads cache based on build.gradle.kts', async () => {
createFile(join(workspace, 'build.gradle.kts'));

await restore('gradle');
expect(spyCacheRestore).toBeCalled();
expect(spyWarning).not.toBeCalled();
expect(spyInfo).toBeCalledWith('gradle cache is not found');
});
});
});
describe('save', () => {
let spyCacheSave: jest.SpyInstance<
ReturnType<typeof cache.saveCache>,
Parameters<typeof cache.saveCache>
>;

beforeEach(() => {
spyCacheSave = jest
.spyOn(cache, 'saveCache')
.mockImplementation((paths: string[], key: string) => Promise.resolve(0));
});

it('throws error if unsupported package manager specified', () => {
return expect(save('ant')).rejects.toThrowError('unknown package manager specified: ant');
});

describe('for maven', () => {
it('uploads cache even if no pom.xml found', async () => {
await save('maven');
expect(spyCacheSave).toBeCalled();
expect(spyWarning).not.toBeCalled();
expect(spyInfo).toBeCalledWith(expect.stringMatching(/^Cache saved with the key:.*/));
});
it('uploads cache', async () => {
createFile(join(workspace, 'pom.xml'));

await save('maven');
expect(spyCacheSave).toBeCalled();
expect(spyWarning).not.toBeCalled();
expect(spyInfo).toBeCalledWith(expect.stringMatching(/^Cache saved with the key:.*/));
});
});
describe('for gradle', () => {
it('uploads cache even if no build.gradle found', async () => {
await save('gradle');
expect(spyCacheSave).toBeCalled();
expect(spyWarning).not.toBeCalled();
expect(spyInfo).toBeCalledWith(expect.stringMatching(/^Cache saved with the key:.*/));
});
it('uploads cache based on build.gradle', async () => {
createFile(join(workspace, 'build.gradle'));

await save('gradle');
expect(spyCacheSave).toBeCalled();
expect(spyWarning).not.toBeCalled();
expect(spyInfo).toBeCalledWith(expect.stringMatching(/^Cache saved with the key:.*/));
});
it('uploads cache based on build.gradle.kts', async () => {
createFile(join(workspace, 'build.gradle.kts'));

await save('gradle');
expect(spyCacheSave).toBeCalled();
expect(spyWarning).not.toBeCalled();
expect(spyInfo).toBeCalledWith(expect.stringMatching(/^Cache saved with the key:.*/));
});
});
});
});

function createFile(path: string) {
core.info(`created a file at ${path}`);
fs.writeFileSync(path, '');
}

function projectRoot(workspace: string): string {
if (os.platform() === 'darwin') {
return `/private${workspace}`;
} else {
return workspace;
}
}
12 changes: 12 additions & 0 deletions __tests__/cache/gradle/.gitignore
@@ -0,0 +1,12 @@
.gradle
**/build/
!src/**/build/

# Ignore Gradle GUI config
gradle-app.setting

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

# Cache of project
.gradletasknamecache
17 changes: 17 additions & 0 deletions __tests__/cache/gradle/build.gradle
@@ -0,0 +1,17 @@
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.codehaus.groovy:groovy:1.8.6'
}
tasks.register('downloadDependencies') {
doLast {
def total = configurations.compileClasspath.inject (0) { sum, file ->
sum + file.length()
}
println total
}
}
11 changes: 11 additions & 0 deletions __tests__/cache/maven/.gitignore
@@ -0,0 +1,11 @@
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
16 changes: 16 additions & 0 deletions __tests__/cache/maven/pom.xml
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.actions</groupId>
<artifactId>setup-java-maven-example</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>