Skip to content

Commit

Permalink
Merge pull request #1319 from pjonsson/remove-javac-dependency
Browse files Browse the repository at this point in the history
Remove compilation of LibN.java
  • Loading branch information
nfi authored Sep 3, 2023
2 parents 8608f08 + 319145c commit 4b2834d
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 287 deletions.
13 changes: 2 additions & 11 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ dependencies {

// FIXME: add test resources.
sourceSets {
corecomm {
resources {
srcDirs = ['java/org/contikios/cooja/corecomm']
}
}
data {
resources {
srcDirs = ['tools/coffee-manager']
Expand All @@ -57,10 +52,9 @@ sourceSets {
main {
java {
srcDirs = ['java', 'tools/coffee-manager']
exclude 'org/contikios/cooja/corecomm/CoreCommTemplate.java'
}
resources {
srcDirs = [corecomm.resources, data.resources, 'config']
srcDirs = [data.resources, 'config']
}
}
}
Expand Down Expand Up @@ -124,10 +118,7 @@ tasks.register('fullJar', Jar) {
run {
// Bad Cooja location detected with gradle run, explicitly pass -cooja.
doFirst {
args += ['--cooja', "$projectDir",
'--javac', javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(javaVersion)
}.get().executablePath]
args += ['--cooja', "$projectDir"]
}
// Connect stdin to make the MSPSim CLI work, except when running in CI.
if (System.getenv('CI') == null) {
Expand Down
2 changes: 1 addition & 1 deletion java/org/contikios/cooja/Cooja.java
Original file line number Diff line number Diff line change
Expand Up @@ -1687,7 +1687,7 @@ public static void loadQuickHelp(final Object obj) {
*/
public record Config(LogbackColors logColors, boolean vis, String externalToolsConfig,
String nashornArgs, String logDir,
String contikiPath, String coojaPath, String javac) {}
String contikiPath, String coojaPath) {}

public record LogbackColors(String error, String warn, String info, String fallback) {}
private record PathIdentifier(String id, String path) {}
Expand Down
18 changes: 1 addition & 17 deletions java/org/contikios/cooja/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,6 @@ class Main {
@Option(names = "--cooja", paramLabel = "DIR", description = "the Cooja directory")
String coojaPath;

/**
* Option for specifying javac path.
*/
@Option(names = "--javac", paramLabel = "FILE", description = "the javac binary")
String javac;

/**
* Option for specifying external user config file.
*/
Expand Down Expand Up @@ -301,23 +295,13 @@ public static void main(String[] args) {
System.exit(1);
}

if (options.mspSimPlatform == null && options.javac == null) {
System.err.println("Missing required option: '--javac=FILE'");
System.exit(1);
}

if (options.mspSimPlatform == null && !Files.exists(Path.of(options.javac))) {
System.err.println("Java compiler '" + options.javac + "' does not exist");
System.exit(1);
}

if (options.mspSimPlatform == null) { // Start Cooja.
// Use colors that are good on a dark background and readable on a white background.
var colors = new LogbackColors(ANSIConstants.BOLD + "91", "96",
ANSIConstants.GREEN_FG, ANSIConstants.DEFAULT_FG);
var cfg = new Config(colors, options.gui, options.externalUserConfig,
options.nashornArgs,
options.logDir, options.contikiPath, options.coojaPath, options.javac);
options.logDir, options.contikiPath, options.coojaPath);
Cooja.go(cfg, simConfigs);
} else { // Start MSPSim.
var config = new ArgumentManager(options.simulationFiles.toArray(new String[0]));
Expand Down
6 changes: 6 additions & 0 deletions java/org/contikios/cooja/MoteType.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ default long getExecutableAddressOf(File file, int lineNr) {
return -1;
}

/** Called when the mote type is added to the simulation. */
default void added() { }

/** Called when the mote type is removed from the simulation. */
default void removed() { }

class MoteTypeCreationException extends Exception {
private MessageList compilationOutput;

Expand Down
7 changes: 2 additions & 5 deletions java/org/contikios/cooja/Simulation.java
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,7 @@ public MoteType[] getMoteTypes() {
public void addMoteType(MoteType newMoteType) {
Cooja.usedMoteTypeIDs.add(newMoteType.getIdentifier());
moteTypes.add(newMoteType);
newMoteType.added();
moteTypeTriggers.trigger(AddRemove.ADD, newMoteType);
}

Expand All @@ -897,14 +898,10 @@ public void removeMoteType(MoteType type) {
removeMote(m);
}
}

// FIXME: call type.removed(), so ContikiMoteType can call arena.close()
// to unload the shared library.
// For more details, see "Obtaining a symbol lookup" at:
// https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/foreign/SymbolLookup.html
if (moteTypes.remove(type)) {
moteTypeTriggers.trigger(AddRemove.REMOVE, type);
}
type.removed();
}

/**
Expand Down
137 changes: 7 additions & 130 deletions java/org/contikios/cooja/contikimote/ContikiMoteType.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,23 @@

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentScope;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.contikios.cooja.AbstractionLevelDescription;
import org.contikios.cooja.ClassDescription;
import org.contikios.cooja.Cooja;
import org.contikios.cooja.CoreComm;
import org.contikios.cooja.Mote;
import org.contikios.cooja.MoteInterface;
import org.contikios.cooja.ProjectConfig;
Expand Down Expand Up @@ -113,10 +106,9 @@
// Do not bother end-user with warnings about internal Cooja details.
@SuppressWarnings("preview")
public class ContikiMoteType extends BaseContikiMoteType {

private static final Logger logger = LoggerFactory.getLogger(ContikiMoteType.class);
private static int fileCounter = 1;

// Shared Arena since MoteTypes are allocated/removed in different threads.
private final Arena arena = Arena.openShared();
/**
* Communication stacks in Contiki.
*/
Expand Down Expand Up @@ -247,20 +239,9 @@ public boolean loadMoteFirmware(boolean vis) throws MoteTypeCreationException {
if (myCoreComm != null) {
throw new MoteTypeCreationException("Core communicator already used: " + myCoreComm.getClass().getName());
}
Path tmpDir;
try {
tmpDir = Files.createTempDirectory("cooja");
} catch (IOException e) {
logger.warn("Failed to create temp directory:" + e);
return false;
}
tmpDir.toFile().deleteOnExit();

// Create, compile, and load the Java wrapper that loads the C library.

// Allocate core communicator class
final var firmwareFile = getContikiFirmwareFile();
myCoreComm = createCoreComm(tmpDir, firmwareFile);
myCoreComm = new CoreComm(arena.scope(), firmwareFile);

/* Parse addresses using map file
* or output of command specified in external tools settings (e.g. nm -a )
Expand Down Expand Up @@ -788,112 +769,8 @@ public boolean setConfigXML(Simulation simulation,
return configureAndInit(Cooja.getTopParentContainer(), simulation, Cooja.isVisualized());
}

/**
* Generates new source file by reading default source template and replacing
* the class name field.
*
* @param tempDir Directory for temporary files
* @param className Java class name (without extension)
* @throws MoteTypeCreationException If error occurs
*/
private static void generateLibSourceFile(Path tempDir, String className) throws MoteTypeCreationException {
// Create the temporary directory and ensure it is deleted on exit.
File dir = tempDir.toFile();
StringBuilder path = new StringBuilder(tempDir.toString());
// Gradually do mkdir() since mkdirs() makes deleteOnExit() leave the
// directory when Cooja quits.
for (String p : new String[]{"/org", "/contikios", "/cooja", "/corecomm"}) {
path.append(p);
dir = new File(path.toString());
if (!dir.mkdir()) {
throw new MoteTypeCreationException("Could not create temporary directory: " + dir);
}
dir.deleteOnExit();
}
Path dst = Path.of(dir + "/" + className + ".java");
dst.toFile().deleteOnExit();

// Instantiate the CoreComm template into the temporary directory.
try (var input = ContikiMoteType.class.getResourceAsStream('/' + "CoreCommTemplate.java");
var reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(input), UTF_8));
var writer = Files.newBufferedWriter(dst, UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line.replace("CoreCommTemplate", className) + "\n");
}
} catch (Exception e) {
throw new MoteTypeCreationException("Could not generate corecomm source file: " + className + ".java", e);
}
}

/**
* Compiles Java class.
*
* @param tempDir Directory for temporary files
* @param className Java class name (without extension)
* @throws MoteTypeCreationException If Java class compilation error occurs
*/
private static void compileSourceFile(Path tempDir, String className) throws MoteTypeCreationException {
String[] cmd = {Cooja.configuration.javac(),
"-cp", System.getProperty("java.class.path"), "--release", String.valueOf(Runtime.version().feature()),
"--enable-preview",
tempDir + "/org/contikios/cooja/corecomm/" + className + ".java" };
ProcessBuilder pb = new ProcessBuilder(cmd);
Process p;
try {
p = pb.start();
} catch (IOException e) {
throw new MoteTypeCreationException("Could not start Java compiler: " + cmd[0], e);
}

// Try to create a message list with support for GUI - will give not UI if headless.
var compilationOutput = MessageContainer.createMessageList(true);
var stdout = compilationOutput.getInputStream(MessageList.NORMAL);
var stderr = compilationOutput.getInputStream(MessageList.ERROR);
try (var outputStream = p.inputReader(UTF_8);
var errorStream = p.errorReader(UTF_8)) {
int b;
while ((b = outputStream.read()) >= 0) {
stdout.write(b);
}
while ((b = errorStream.read()) >= 0) {
stderr.write(b);
}
if (p.waitFor() == 0) {
File classFile = new File(tempDir + "/org/contikios/cooja/corecomm/" + className + ".class");
classFile.deleteOnExit();
return;
}
} catch (IOException | InterruptedException e) {
throw new MoteTypeCreationException("Could not compile corecomm source file: " + className + ".java", e, compilationOutput);
}
throw new MoteTypeCreationException("Could not compile corecomm source file: " + className + ".java", compilationOutput);
}

/**
* Create and return an instance of the core communicator identified by
* className. This core communicator will load the native library libFile.
*
* @param tempDir Directory for temporary files
* @param libFile Native library file
* @return Core Communicator
*/
private static CoreComm createCoreComm(Path tempDir, File libFile) throws MoteTypeCreationException {
// Loading a class might leave residue in the JVM so use a new name for the next call.
final var className = "Lib" + fileCounter++;
generateLibSourceFile(tempDir, className);
compileSourceFile(tempDir, className);
Class<? extends CoreComm> newCoreCommClass;
try (var loader = URLClassLoader.newInstance(new URL[]{tempDir.toUri().toURL()})) {
newCoreCommClass = loader.loadClass("org.contikios.cooja.corecomm." + className).asSubclass(CoreComm.class);
} catch (IOException | NullPointerException | ClassNotFoundException e1) {
throw new MoteTypeCreationException("Could not load corecomm class file: " + className + ".class", e1);
}

try {
return newCoreCommClass.getConstructor(File.class).newInstance(libFile);
} catch (Exception e) {
throw new MoteTypeCreationException("Error when creating corecomm instance: " + className, e);
}
@Override
public void removed() {
arena.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,50 +26,60 @@
*
*/

package org.contikios.cooja;
package org.contikios.cooja.contikimote;

import java.io.File;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.SegmentScope;
import java.lang.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;

/**
* The purpose of CoreComm is to communicate with a compiled Contiki system
* using Java Native Interface (JNI). Each implemented class (named
* Lib[number]), loads a shared library which belongs to one mote type. The
* reason for this somewhat strange design is that once loaded, a native library
* cannot be unloaded in Java (in the current versions available). Therefore, if
* we wish to load several libraries, the names and associated native functions
* must have unique names. And those names are defined via the calling class in
* JNI. For example, the corresponding function for a native tick method in
* class Lib1 will be named Java_org_contikios_cooja_corecomm_Lib1_tick. When creating
* a new mote type, the main Contiki source file is built with function
* names compatible with the next available corecomm class. This also implies
* that even if a mote type is deleted, a new one cannot be created using the
* same corecomm class without restarting the JVM and thus the entire
* simulation.
* <p>
* Each implemented CoreComm class needs read-access to the following core
* variables:
* <ul>
* <li>referenceVar
* </ul>
* and the following native functions:
* <ul>
* <li>tick()
* <li>init()
* <li>getReferenceAbsAddr()
* <li>getMemory(int start, int length, byte[] mem)
* <li>setMemory(int start, int length, byte[] mem)
* </ul>
*
* @author Fredrik Osterlind
* CoreComm loads a library file and keeps track of the method handles
* to the tick and getReferenceAddress methods.
*/
public interface CoreComm {
// Do not bother end-user with warnings about internal Cooja details.
@SuppressWarnings("preview")
class CoreComm {
private final SymbolLookup symbols;
private final MethodHandle coojaTick;
/**
* Loads library libFile with a scope.
*
* @param scope Scope to load the library file in
* @param libFile Library file
*/
CoreComm(SegmentScope scope, File libFile) {
symbols = SymbolLookup.libraryLookup(libFile.getAbsolutePath(), scope);
var linker = Linker.nativeLinker();
coojaTick = linker.downcallHandle(symbols.find("cooja_tick").get(),
FunctionDescriptor.ofVoid());
// Call cooja_init() in Contiki-NG.
var coojaInit = linker.downcallHandle(symbols.find("cooja_init").get(),
FunctionDescriptor.ofVoid());
try {
coojaInit.invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Calling cooja_init failed: " + e.getMessage(), e);
}
}

/**
* Ticks a mote once.
*/
void tick();
void tick() {
try {
coojaTick.invokeExact();
} catch (Throwable e) {
throw new RuntimeException("Calling cooja_tick failed: " + e.getMessage(), e);
}
}

/**
* Returns the absolute memory address of the reference variable.
*/
long getReferenceAddress();
long getReferenceAddress() {
return symbols.find("referenceVar").get().address();
}
}
Loading

0 comments on commit 4b2834d

Please sign in to comment.