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

Adds clear option to ProjectGenerate instruction #5369

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Empty file.
@@ -0,0 +1,9 @@
package foo.bar;


public class Buildpath {
public final static String VERSION = "${def;Bundle-Version;1.0.0}";
public final static String[] BUILDPATH = {
${template;-buildpath;"${@}"}
};
}
Expand Up @@ -108,6 +108,74 @@ public void testSimpleGenerator() throws Exception {
}
}

@Test
@SuppressWarnings({
"unchecked", "rawtypes"
})
public void testSimpleGeneratorDontClearOutput() throws Exception {
try (Workspace ws = getWorkspace("resources/ws-stalecheck")) {
getRepo(ws);
Project project = ws.getProject("p5");
project.setProperty("-generate",
"gen/**.java;output=src-gen/;generate='javagen -o src-gen/ gen/**.java';clear=false");

File outputdir = project.getFile("src-gen");

assertThat(project.getGenerate()
.getOutputDirs()).contains(outputdir);

File in = project.getFile("gen/Buildpath.java");
in.setLastModified(System.currentTimeMillis() - 2000);

File out = project.getFile("src-gen/foo/bar/Buildpath.java");
assertThat(out).doesNotExist();

File existing = project.getFile("src-gen/test.txt");
assertThat(existing).doesNotExist();

assertThat(project.getGenerate()
.needsBuild()).isTrue();

project.getGenerate()
.generate(true);
assertThat(project.check()).isTrue();
assertThat(out).isFile();

assertThat(project.getGenerate()
.needsBuild()).isFalse();

// new we simulate modification of a source by adding a file
existing.createNewFile();

// no reason to build
assertThat(project.getGenerate()
.needsBuild()).isFalse();

// delete the generated file
IO.delete(out);
// in some rear cases the whole process seamed to be to fast and the
// created file had the exact same timestamp. So we make sure the
// source is newer
in.setLastModified(System.currentTimeMillis() + 1);

assertThat(out).doesNotExist();

assertThat(project.getGenerate()
.needsBuild()).isTrue();

project.getGenerate()
.generate(true);

// check that our test file is still there and does exist
assertThat(existing).isFile();
assertThat(project.check()).isTrue();
assertThat(out).isFile();

assertThat(project.getGenerate()
.needsBuild()).isFalse();
}
}

@Test
@SuppressWarnings({
"unchecked", "rawtypes"
Expand Down
3 changes: 1 addition & 2 deletions biz.aQute.bndlib/src/aQute/bnd/build/Project.java
Expand Up @@ -2444,8 +2444,7 @@ public void clean() throws Exception {
clean(getTargetDir(), "target");
clean(getSrcOutput(), "source output");
clean(getTestOutput(), "test output");
for (File output : getGenerate().getOutputDirs())
clean(output, "generate output " + output, false);
getGenerate().clean();

for (File src : getSourcePath()) {
IO.mkdirs(src);
Expand Down
42 changes: 33 additions & 9 deletions biz.aQute.bndlib/src/aQute/bnd/build/ProjectGenerate.java
Expand Up @@ -4,6 +4,7 @@
import static aQute.bnd.result.Result.ok;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
Expand Down Expand Up @@ -96,12 +97,27 @@ private Result<Void> prepare(String sourceWithDuplicate, GeneratorSpec st) {

File out = project.getFile(output);
if (out.isDirectory()) {
for (File f : out.listFiles()) {
IO.delete(f);
if (st.clear()
.orElseGet(() -> Boolean.TRUE)) {
for (File f : out.listFiles()) {
IO.delete(f);
}
}
} else {
out.mkdirs();
}

long latestModifiedSource = sourceFiles.stream()
.mapToLong(File::lastModified)
.max()
.getAsLong();

// the out folder serves as our trigger if a build is needed. Thus we
// use to output directory timestamp as marker we can compare against
// later. If clear is false a generator might decided to do nothing and
// could cause a build loop.
out.setLastModified(latestModifiedSource);

return Result.ok(null);
}

Expand Down Expand Up @@ -308,9 +324,15 @@ public Result<Set<File>> getInputs() {
}

public Set<File> getOutputDirs() {
return getOutputDirs(false);
}

private Set<File> getOutputDirs(boolean toClear) {
return project.instructions.generate()
.values()
.stream()
.filter(spec -> !toClear || spec.clear()
.orElse(Boolean.TRUE))
.map(GeneratorSpec::output)
.filter(Objects::nonNull)
.map(project::getFile)
Expand Down Expand Up @@ -349,11 +371,9 @@ public boolean needsBuild() {
if (outputFiles.isEmpty())
return true;

long latestModifiedTarget = outputFiles.stream()
.filter(File::isFile)
.mapToLong(File::lastModified)
.min()
.orElse(0);
File out = project.getFile(output);

long latestModifiedTarget = out.lastModified();

boolean staleFiles = latestModifiedSource > latestModifiedTarget;
if (staleFiles)
Expand All @@ -363,8 +383,12 @@ public boolean needsBuild() {
}

public void clean() {
getOutputDirs().stream()
.forEach(IO::delete);
for (File output : getOutputDirs(true))
try {
project.clean(output, "generate output " + output, false);
} catch (IOException e) {
Exceptions.duck(e);
}
}

@Override
Expand Down
Expand Up @@ -61,6 +61,9 @@ interface GeneratorSpec {

@SyntaxAnnotation(lead = "Specify a JAR version for a Main class plugin (name in generate must be a fqn class name)")
Optional<String> version();

@SyntaxAnnotation(lead = "Determins if the output directory needs to be cleared before the generator runs. The default is true.")
Optional<Boolean> clear();
}

}
12 changes: 7 additions & 5 deletions docs/_instructions/generate.md
Expand Up @@ -9,17 +9,20 @@ since: 5.1
Virtually all the work bnd is concerned about happens in generating the JAR file. The key idea is to _pull_ resources in the JAR, instead of the more traditional _push_ model of other builders. This works well, except for _generating source code_. This generating step must happen before the compiler is called, and the compiler is generally called before bnd becomes active.

This `-generate` instruction specifies the code generating steps that must be executed. Source code can be generated by _system_ commands or the bnd _external plugins_.

-generate ::= clause ( ',' clause )*
clause ::= FILESET ';' 'output=' DIR (';' option )*
src ::= FILESET
option ::= 'system=' STRING
| 'generate=' STRING
| 'classpath=' PATH
| 'workingdir=' FILE
| 'classpath=' PATH
| 'workingdir=' FILE
| 'clear=' BOOLEAN

For each clause, the key of the clause is used to establish an Ant File Set, e.g. `foo/**.in`. This a glob expression with the exception that the double wildcard ('**') indicates to any depth of directories. The `output` attribute _must_ specify a directory. If the output must be compiled this directory must be on the bnd source path.

The output directory will created if it does not exist. It will be cleared of any previous generate results before a run, except if the option `clear` is set to false. In this case the used Generator needs to deal with the remnants in the directory itself.

If any file in the source is older than any file in the target (to any depth), or the target is empty, the clause is considered _stale_. If the clause is not stale, it is further ignored. If no further options are set on the clause, a warning is generated that some files are out of date.

If either a warning or error option is given, these will be executed on the project.
Expand Down Expand Up @@ -56,7 +59,7 @@ If this example is used, it is necessary to add a new _source folder_. In Eclips
src=${^src},src-gen

Assuming that the input files are in the `gen` directory, the following can be used to automatically generate the output files based on the input files.

-generate: \
gen/**.java; \
output='src-gen/' ; \
Expand Down Expand Up @@ -128,4 +131,3 @@ If you the plugin source code is in the same workspace as the project using this

You can take a look at the [JavaGen](https://github.com/bndtools/bnd/tree/master/biz.aQute.bnd.javagen) project in the bnd build to see how an actual external plugin is made.