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

[resolve] Add cache mode #5304

Closed
wants to merge 2 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ enum ResolveMode {
@SyntaxAnnotation(lead = "A resolve will take place before launching when in batch mode (e.g. Gradle) but not in IDE mode (e.g. Eclipse)")
batch,

/**
* Run the resolver before launching during batch mode
*/
@SyntaxAnnotation(lead = "Resolve when the runbundles are needed unless there is a cache file that is newer than the bndrun/project & workspace. The cache file has the same name as the project/bndrun file but starts with a '.'")
cache,
}

@SyntaxAnnotation(lead = "Resolve mode defines when resolving takes place. The default, manual, requires a manual step in bndtools. Auto will resolve on save, and beforelaunch runs the resolver before being launched, batchlaunch is like beforelaunch but only in batch mode", example = "'-resolve manual", pattern = "(manual|auto|beforelaunch|batch)")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
@org.osgi.annotation.versioning.Version("1.4.0")
@org.osgi.annotation.versioning.Version("1.5.0")
package aQute.bnd.help.instructions;
117 changes: 117 additions & 0 deletions biz.aQute.resolve/src/biz/aQute/resolve/Bndrun.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package biz.aQute.resolve;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.osgi.resource.Requirement;
Expand All @@ -13,12 +15,14 @@
import aQute.bnd.build.Container;
import aQute.bnd.build.Run;
import aQute.bnd.build.Workspace;
import aQute.bnd.build.WorkspaceLayout;
import aQute.bnd.build.model.BndEditModel;
import aQute.bnd.build.model.clauses.HeaderClause;
import aQute.bnd.build.model.clauses.VersionedClause;
import aQute.bnd.build.model.conversions.CollectionFormatter;
import aQute.bnd.build.model.conversions.Converter;
import aQute.bnd.build.model.conversions.HeaderClauseFormatter;
import aQute.bnd.exceptions.Exceptions;
import aQute.bnd.header.Parameters;
import aQute.bnd.help.Syntax;
import aQute.bnd.help.instructions.ResolutionInstructions;
Expand All @@ -27,6 +31,9 @@
import aQute.bnd.osgi.Processor;
import aQute.bnd.osgi.resource.FilterParser;
import aQute.bnd.osgi.resource.FilterParser.Expression;
import aQute.lib.io.IO;
import aQute.lib.utf8properties.UTF8Properties;
import aQute.libg.cryptography.SHA1;

/**
* This is a resolving version of the Run class. The name of this class is known
Expand Down Expand Up @@ -171,6 +178,9 @@ public Collection<Container> getRunbundles() throws Exception {
default :
break;

case cache :
return cache();

case batch :
if (!gestalt.containsKey(Constants.GESTALT_BATCH) && !super.getRunbundles().isEmpty())
break;
Expand All @@ -185,4 +195,111 @@ public Collection<Container> getRunbundles() throws Exception {
return super.getRunbundles();
}

enum CacheReason {
NO_CACHE_FILE,
NOT_A_BND_LAYOUT,
CACHE_STALE_PROJECT,
CACHE_STALE_WORKSPACE,
USE_CACHE,
INVALID_CACHE;

}

CacheReason testReason;

private Collection<Container> cache() throws Exception {
Workspace ws = getWorkspace();
File ours = getPropertiesFile();
Copy link
Member

Choose a reason for hiding this comment

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

What about included files in the bndrun file and in the workspace?

In the gradle plugin all of the files factor in to out-of-date checks:

private ConfigurableFileCollection bndConfiguration() {
Workspace bndWorkspace = bndProject.getWorkspace();
return objects.fileCollection()
.from(bndWorkspace.getPropertiesFile(), bndWorkspace.getIncluded(), bndProject.getPropertiesFile(),
bndProject.getIncluded());
}

Copy link
Member Author

Choose a reason for hiding this comment

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

makes sense

File cache = getCacheFile(ours);

long cacheLastModified = cache.lastModified();

CacheReason reason;
if (ws.getLayout() != WorkspaceLayout.BND)
reason = CacheReason.NOT_A_BND_LAYOUT;
else if (!cache.isFile())
reason = CacheReason.NO_CACHE_FILE;
else if (cacheLastModified <= ws.lastModified())
reason = CacheReason.CACHE_STALE_WORKSPACE;
else if (cacheLastModified <= lastModified())
reason = CacheReason.CACHE_STALE_PROJECT;
else
reason = CacheReason.USE_CACHE;

testReason = reason;

trace("force = %s", reason);

try (Processor p = new Processor()) {

if (cache.isFile())
p.setProperties(cache);

if (reason == CacheReason.USE_CACHE) {
trace("attempting to use cache");
String runbundles = p.getProperty(Constants.RUNBUNDLES);
Collection<Container> containers = parseRunbundles(runbundles);
if (isAllOk(containers)) {
trace("from cache %s", containers);
return containers;
} else {
testReason = CacheReason.INVALID_CACHE;
trace("the cached bundles were not ok, will resolve");
}
}
trace("resolving");

IO.delete(cache);
if (cache.isFile()) {
throw new IllegalStateException("cannot delete cache file " + cache);
}

RunResolution resolved = RunResolution.resolve(this, Collections.emptyList());
if (!resolved.isOK()) {
throw new IllegalStateException(resolved.report(false));
}

String spec = resolved.getRunBundlesAsString();

List<Container> containers = parseRunbundles(spec);
if (isAllOk(containers)) {
UTF8Properties props = new UTF8Properties(p.getProperties());
props.setProperty(Constants.RUNBUNDLES, spec);
cache.getParentFile()
.mkdirs();
props.store(cache);
}
return containers;
}
}


/**
* Return the file used to cache the resolved solution for the given file
*
* @param file the file to find the cached file for
* @return the cached file
*/
public File getCacheFile(File file) {
try {
String path = file.getAbsolutePath();
String digest = SHA1.digest(path.getBytes(StandardCharsets.UTF_8))
.asHex();
return getWorkspace().getCache("resolved-cache/".concat(digest)
.concat(".resolved"));
} catch (Exception e) {
// not gonna happen
throw Exceptions.duck(e);
}
}

private boolean isAllOk(Collection<Container> containers) {
for (Container c : containers) {
if (c.getError() != null) {
return false;
}
}
return true;
}

}
2 changes: 1 addition & 1 deletion biz.aQute.resolve/src/biz/aQute/resolve/package-info.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@Version("8.1.0")
@Version("8.2.0")
package biz.aQute.resolve;

import org.osgi.annotation.versioning.Version;
101 changes: 101 additions & 0 deletions biz.aQute.resolve/test/biz/aQute/resolve/RunResolutionTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package biz.aQute.resolve;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -26,6 +28,7 @@
import aQute.bnd.result.Result;
import aQute.bnd.test.jupiter.InjectTemporaryDirectory;
import aQute.lib.io.IO;
import biz.aQute.resolve.Bndrun.CacheReason;

public class RunResolutionTest {

Expand Down Expand Up @@ -118,6 +121,104 @@ public void testCachingOfResult() throws Exception {
// assertThat(runbundles).hasSize(22);
}

@Test
public void testResolveCachedWithStandalone() throws Exception {
Bndrun bndrun = Bndrun.createBndrun(workspace, IO.getFile(tmp.toFile(), "resolver.bndrun"));
bndrun.setProperty("-resolve", "cache");
Collection<Container> runbundles = bndrun.getRunbundles();
assertThat(bndrun.testReason).isEqualTo(CacheReason.NOT_A_BND_LAYOUT);
}

@Test
public void testResolveCached() throws Exception {

Bndrun bndrun = Bndrun.createBndrun(workspace, IO.getFile(ws.toFile(), "test.simple/resolve.bndrun"));
bndrun.setTrace(true);
File file = bndrun.getPropertiesFile();
assertTrue(bndrun.check());
File cache = bndrun.getCacheFile(file);

System.out.println("get the embedded list of runbundles, this is out benchmark");
bndrun.setProperty("-resolve", "manual");
Collection<Container> manual = bndrun.getRunbundles();
assertThat(manual).hasSize(2);

System.out.println("remove the embedded list and set mode to 'cache'");
bndrun.setProperty("-resolve", "cache");
bndrun.unsetProperty("-runbundles");

assertThat(cache).doesNotExist();

System.out.println("First time we should resolve & create a cache file");
Collection<Container> cached = bndrun.getRunbundles();
assertTrue(bndrun.check());
assertThat(cache).isFile();
assertThat(cached).containsExactlyElementsOf(manual);
assertThat(cache.lastModified()).isGreaterThan(bndrun.lastModified());
assertThat(bndrun.testReason).isEqualTo(CacheReason.NO_CACHE_FILE);

System.out
.println("Second time, the cache file should used, so make it valid but empty ");
long lastModified = cache.lastModified();
IO.store("-runbundles ", cache);
cached = bndrun.getRunbundles();
assertTrue(bndrun.check());
assertThat(cached).isEmpty();
assertThat(bndrun.testReason).isEqualTo(CacheReason.USE_CACHE);

System.out.println("Now make cache invalid, should be ignored");
IO.store("-runbundles is not a valid file", cache);
cached = bndrun.getRunbundles();
assertTrue(bndrun.check());
assertThat(cached).containsExactlyElementsOf(manual);
assertThat(bndrun.testReason).isEqualTo(CacheReason.INVALID_CACHE);

System.out.println("Now empty cache, but still use it");
IO.store("-runbundles ", cache);
cached = bndrun.getRunbundles();
assertTrue(bndrun.check());
assertThat(cached).isEmpty();
assertThat(bndrun.testReason).isEqualTo(CacheReason.USE_CACHE);

System.out.println("Refresh and check we still use the cache");
bndrun.refresh();
bndrun.setProperty("-resolve", "cache");
bndrun.unsetProperty("-runbundles");
cached = bndrun.getRunbundles();
assertThat(cached).isEmpty();
assertThat(bndrun.testReason).isEqualTo(CacheReason.USE_CACHE);

System.out.println("Update an include file, refresh and check we still sue the cache");
File empty = IO.getFile(ws.toFile(), "test.simple/empty-included-in-resolve.bnd");
long now = System.currentTimeMillis();
empty.setLastModified(now);
assertThat(bndrun.lastModified()).isLessThan(now);
bndrun.refresh();
bndrun.setProperty("-resolve", "cache");
bndrun.unsetProperty("-runbundles");
cached = bndrun.getRunbundles();
assertTrue(bndrun.check());
assertThat(cached).containsExactlyElementsOf(manual);
assertThat(bndrun.testReason).isEqualTo(CacheReason.CACHE_STALE_PROJECT);

System.out.println("Next we use the cache");
cached = bndrun.getRunbundles();
assertThat(cached).containsExactlyElementsOf(manual);
assertThat(bndrun.testReason).isEqualTo(CacheReason.USE_CACHE);

Thread.sleep(100);

System.out.println("Update the cnf/build file");
File build = IO.getFile(ws.toFile(), "cnf/build.bnd");
now = System.currentTimeMillis();
build.setLastModified(now);
workspace.refresh();
cached = bndrun.getRunbundles();
assertTrue(bndrun.check());
assertThat(cached).containsExactlyElementsOf(manual);
assertThat(bndrun.testReason).isEqualTo(CacheReason.CACHE_STALE_WORKSPACE);
}

@Test
public void testNotCachingOfResultForOtherResolveOption() throws Exception {
Bndrun bndrun = Bndrun.createBndrun(workspace, IO.getFile(tmp.toFile(), "resolver.bndrun"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-include empty-included-in-resolve.bnd
-runfw: org.eclipse.osgi;version='[3.13.0,3.13.1)'
-runee: JavaSE-1.8
-runrequires: osgi.identity;filter:='(osgi.identity=test.simple)'
Expand Down
4 changes: 3 additions & 1 deletion docs/_instructions/resolve.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
layout: default
class: Workspace
title: -resolve (manual|auto|beforelaunch|batch)
title: -resolve (manual|auto|beforelaunch|batch|cache)
summary: Defines when/how resolving is done to calculate the -runbundles
---

Expand All @@ -13,6 +13,8 @@ The values are:
* `auto` – Whenever the initial requirements are saved, the resolver will be used to set new `-runbundles`
* `beforelaunch` – Calculate the `-runbundles` on demand. This ignores the value of the `-runbundles` and runs the resolver. The results of the resolver are cached. This cache works by creating a checksum over all the properties of the project.
* `batch` – When running in batch mode, the run bundles will be resolved. In all other modes this will only resolve when the `-runbundles` are empty.
* `cache` – Will use a cache file in the workspace cache. If that file is stale relative to the workspace or project or it does not exist, then the bnd(run) file will be resolved and the result is stored in the cache file.


## Example

Expand Down