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

Remove compilation of LibN.java #1319

Merged
merged 3 commits into from
Sep 3, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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