Add an instrument operation for ahead-of-time RIFE2 instrumentation
Some checks are pending
bld-ci / build (./bld, 17, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 21, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 25, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 26, macos-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 17, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 21, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 25, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 26, windows-latest, false) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 17, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 21, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 25, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 26, ubuntu-latest, true) (push) Waiting to run
javadocs-pages / deploy (push) Waiting to run

This commit is contained in:
Geert Bevin 2026-07-20 19:52:25 -04:00
parent 682fb05d15
commit 30937a4d96
4 changed files with 253 additions and 0 deletions

View file

@ -25,6 +25,7 @@ public class Project extends BaseProject {
private final JavadocOperation javadocOperation_ = new JavadocOperation();
private final PrecompileOperation precompileOperation_ = new PrecompileOperation();
private final InstrumentOperation instrumentOperation_ = new InstrumentOperation();
private final JarOperation jarOperation_ = new JarOperation();
private final JarOperation jarSourcesOperation_ = new JarOperation();
private final JarOperation jarJavadocOperation_ = new JarOperation();
@ -56,6 +57,16 @@ public class Project extends BaseProject {
return precompileOperation_;
}
/**
* Retrieves the project's default instrument operation.
*
* @return the default instrument operation instance
* @since 2.4
*/
public InstrumentOperation instrumentOperation() {
return instrumentOperation_;
}
/**
* Retrieves the project's default jar operation.
*
@ -125,6 +136,19 @@ public class Project extends BaseProject {
precompileOperation().executeOnce(() -> precompileOperation().fromProject(this));
}
/**
* Standard build command, instruments the compiled classes ahead of
* time, as an alternative to the java agent.
*
* @since 2.4
*/
@BuildCommand(help = InstrumentHelp.class)
public void instrument()
throws Exception {
compile();
instrumentOperation().executeOnce(() -> instrumentOperation().fromProject(this));
}
/**
* Standard build command, creates a jar archive for the project.
*

View file

@ -0,0 +1,31 @@
/*
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.bld.help;
import rife.bld.CommandHelp;
import rife.tools.StringUtils;
/**
* Provides help for the instrument command.
*
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
* @since 2.4
*/
public class InstrumentHelp implements CommandHelp {
public String getSummary() {
return "Instruments compiled classes ahead of time, as an alternative to the java agent";
}
public String getDescription(String topic) {
return StringUtils.replace("""
Instruments the compiled classes ahead of time with RIFE2's bytecode
transformations, as an alternative to the java agent: web engine
continuations, workflow continuations, meta-data merging and
lazy-loading. This makes the agent unnecessary at run time and
enables these capabilities inside a GraalVM native image.
Usage : ${topic}""", "${topic}", topic);
}
}

View file

@ -0,0 +1,140 @@
/*
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.bld.operations;
import rife.bld.BaseProject;
import rife.tools.FileUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Instruments compiled classes ahead of time with RIFE2's bytecode
* transformations, as an alternative to the java agent.
* <p>The instrumentation is performed by
* {@code rife.instrument.InstrumentationDeployer} from the RIFE2
* dependency of the project that is being built, so the same
* transformations are applied as the ones the agent of that RIFE2 version
* performs at class loading time: web engine continuations, workflow
* continuations, meta-data merging and lazy-loading. Classes that don't
* use any of these capabilities are left untouched, and instrumenting
* already instrumented classes makes no changes.
* <p>Ahead-of-time instrumented classes make the agent unnecessary at run
* time and are the way to use these capabilities inside a GraalVM native
* image.
*
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
* @since 2.4
*/
public class InstrumentOperation extends AbstractProcessOperation<InstrumentOperation> {
public static final String DEPLOYER_CLASS = "rife.instrument.InstrumentationDeployer";
private final List<File> sourceDirectories_ = new ArrayList<>();
private File destinationDirectory_;
/**
* Part of the {@link #execute} operation, constructs the command list
* to use for building the process.
*
* @since 2.4
*/
protected List<String> executeConstructProcessCommandList() {
var args = new ArrayList<String>();
args.add(javaTool());
args.addAll(javaOptions());
if (!classpath().isEmpty()) {
args.add("-cp");
args.add(FileUtils.joinPaths(classpath()));
}
args.add(DEPLOYER_CLASS);
args.add("-verbose");
args.add("-d");
args.add(destinationDirectory().getAbsolutePath());
for (var directory : sourceDirectories()) {
args.add(directory.getAbsolutePath());
}
return args;
}
/**
* Configures an instrument operation from a {@link BaseProject}.
* <p>The project's main build directory is instrumented in place, with
* the runtime classpath providing the RIFE2 dependency that performs
* the instrumentation.
*
* @param project the project to configure the instrument operation from
* @since 2.4
*/
public InstrumentOperation fromProject(BaseProject project) {
return classpath(project.runClasspath())
.sourceDirectories(project.buildMainDirectory())
.destinationDirectory(project.buildMainDirectory());
}
/**
* Provides source directories that will be instrumented.
*
* @param directories source directories
* @return this operation instance
* @since 2.4
*/
public InstrumentOperation sourceDirectories(File... directories) {
sourceDirectories_.addAll(List.of(directories));
return this;
}
/**
* Provides a list of source directories that will be instrumented.
* <p>A copy will be created to allow this list to be independently modifiable.
*
* @param directories a list of source directories
* @return this operation instance
* @since 2.4
*/
public InstrumentOperation sourceDirectories(Collection<File> directories) {
sourceDirectories_.addAll(directories);
return this;
}
/**
* Provides the destination directory in which the instrumented classes
* will be stored.
*
* @param directory the instrumentation destination directory
* @return this operation instance
* @since 2.4
*/
public InstrumentOperation destinationDirectory(File directory) {
destinationDirectory_ = directory;
return this;
}
/**
* Retrieves the source directories that will be instrumented.
* <p>This is a modifiable list that can be retrieved and changed.
*
* @return the instrumentation source directories
* @since 2.4
*/
public List<File> sourceDirectories() {
return sourceDirectories_;
}
/**
* Retrieves the destination directory in which the instrumented classes
* will be stored.
*
* @return the instrumentation destination directory
* @since 2.4
*/
public File destinationDirectory() {
return destinationDirectory_;
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.bld.operations;
import org.junit.jupiter.api.Test;
import java.io.File;
import static org.junit.jupiter.api.Assertions.*;
public class TestInstrumentOperation {
@Test
void testInstantiation() {
var operation = new InstrumentOperation();
assertEquals("java", operation.javaTool());
assertTrue(operation.javaOptions().isEmpty());
assertTrue(operation.classpath().isEmpty());
assertTrue(operation.sourceDirectories().isEmpty());
assertNull(operation.destinationDirectory());
}
@Test
void testPopulation() {
var source1 = new File("source1");
var source2 = new File("source2");
var destination = new File("destination");
var operation = new InstrumentOperation()
.classpath("classpath1", "classpath2")
.sourceDirectories(source1, source2)
.destinationDirectory(destination);
assertEquals(2, operation.classpath().size());
assertEquals(2, operation.sourceDirectories().size());
assertEquals(destination, operation.destinationDirectory());
}
@Test
void testProcessCommandList() {
var operation = new InstrumentOperation()
.classpath("cp1", "cp2")
.sourceDirectories(new File("classes"), new File("more-classes"))
.destinationDirectory(new File("instrumented"));
var command = operation.executeConstructProcessCommandList();
assertEquals("java", command.get(0));
assertTrue(command.contains("-cp"));
assertTrue(command.contains("cp1" + File.pathSeparator + "cp2"));
assertTrue(command.contains(InstrumentOperation.DEPLOYER_CLASS));
assertTrue(command.contains("-verbose"));
var d_index = command.indexOf("-d");
assertTrue(d_index > 0);
assertEquals(new File("instrumented").getAbsolutePath(), command.get(d_index + 1));
assertEquals(new File("classes").getAbsolutePath(), command.get(command.size() - 2));
assertEquals(new File("more-classes").getAbsolutePath(), command.get(command.size() - 1));
}
}