diff --git a/.classpath b/.classpath new file mode 100644 index 00000000..9b6a52c8 --- /dev/null +++ b/.classpath @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..e7e9d11d --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..0255334f --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 00000000..03e48d4b --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..b3e9cbd3 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..25d34a47 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 00000000..e96534fb --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..3701c312 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: java +install: true + +os: linux +dist: bionic +jdk: +- oraclejdk9 +- oraclejdk11 + +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ + +cache: + directories: + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ + +script: + - ./gradlew check -s + diff --git a/README.md b/README.md index 4c8cf55c..6c82f501 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,10 @@ default: _appmap.yml_ `appmap.output.directory` specify the output directory of `appmap.json` files default: _./_ + + +# Build status + +[![Build Status](https://travis-ci.com/applandinc/appmap-java.svg?branch=master)](https://travis-ci.org/applandinc/appmap-java) + + diff --git a/build.gradle b/build.gradle index d74c4eb0..78a8aab2 100644 --- a/build.gradle +++ b/build.gradle @@ -7,22 +7,16 @@ */ plugins { - id 'application' + id 'java' id 'com.github.johnrengelman.shadow' version '5.2.0' } -application { - mainClassName = 'com.appland.appmap.App' -} - repositories { jcenter() mavenCentral() } dependencies { - implementation 'com.google.guava:guava:28.0-jre' - implementation 'info.picocli:picocli:4.0.4' implementation 'org.yaml:snakeyaml:1.25' implementation 'com.alibaba:fastjson:1.2.61' implementation 'org.javassist:javassist:3.25.0-GA' @@ -36,7 +30,7 @@ dependencies { jar { manifest { - attributes 'Premain-Class': application.mainClassName + attributes 'Premain-Class': 'com.appland.appmap.Agent' } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf0..cc4fdc29 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7c4388a9..94920145 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 83f2acfd..2fe81a7d 100755 --- a/gradlew +++ b/gradlew @@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -175,14 +175,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/src/main/java/com/appland/appmap/Agent.java b/src/main/java/com/appland/appmap/Agent.java new file mode 100644 index 00000000..3f3149aa --- /dev/null +++ b/src/main/java/com/appland/appmap/Agent.java @@ -0,0 +1,58 @@ +package com.appland.appmap; + +import com.appland.appmap.config.AppMapConfig; +import com.appland.appmap.record.RuntimeRecorder; +import com.appland.appmap.transform.ClassFileTransformer; + +import java.io.File; +import java.lang.instrument.Instrumentation; + +/** + * Agent is a JVM agent which instruments, records, and prints appmap files + * for a program. To use the AppMap agent, start the progress with the JVM argument + * -javaagent:/path/to/appmap-java.jar. The agent will read + * the appmap.yml configuration file, which tells it which classes + * to instrument. Classes will be instrumented automatically as they are loaded by the + * JVM. As instrumented classes are used by the program, the activity is recorded by the agent. + * In some cases, such as JUnit, AppMap files will be printed as the program executes. + * When the agent exits, any un-printed data will be written to the file appmap.json. + */ +public class Agent { + + private final static String DEFAULT_CONFIG_FILE = "appmap.yml"; + + /** + * premain is the entry point for the AppMap Java agent. + * @param agentArgs agent options + * @param inst services needed to instrument Java programming language code + * @see Package java.lang.instrument + */ + public static void premain(String agentArgs, Instrumentation inst) { + System.err.println("AppMap agent is loaded"); + + String appmapPath = System.getProperty("appmap.config.file"); + if (appmapPath == null) { + appmapPath = DEFAULT_CONFIG_FILE; + } + + System.err.println("Reading appmap configuration from " + appmapPath); + + if (AppMapConfig.load(new File(appmapPath)) == null) { + System.err.printf("AppMap: failed to load config %s\n", appmapPath); + return; + } + + inst.addTransformer(new ClassFileTransformer()); + + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + public void run() { + RuntimeRecorder runtimeRecorder = RuntimeRecorder.get(); + if (runtimeRecorder.isEmpty()) { + return; + } + + runtimeRecorder.flushToFile("appmap.json"); + } + }, "Shutdown-thread")); + } +} \ No newline at end of file diff --git a/src/main/java/com/appland/appmap/App.java b/src/main/java/com/appland/appmap/App.java deleted file mode 100644 index e83bb44b..00000000 --- a/src/main/java/com/appland/appmap/App.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.appland.appmap; - -import java.lang.instrument.Instrumentation; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.PrintWriter; - -import com.appland.appmap.commands.Inspect; -import com.appland.appmap.commands.Record; -import com.appland.appmap.commands.Upload; -import com.appland.appmap.config.AppMapConfig; -import com.appland.appmap.record.RuntimeRecorder; -import com.appland.appmap.transform.ClassFileTransformer; - -import picocli.CommandLine; -import picocli.CommandLine.Command; - -@Command(name = "appmap", - mixinStandardHelpOptions = true, - subcommands = { - Inspect.class, - Record.class, - Upload.class, -}) -public class App implements Runnable { - - private final static String DEFAULT_CONFIG_FILE = "appmap.yml"; - - @Override public void run() { } - - /** - * This method is the entry point of the CLI. - */ - public static void main(String[] args) { - CommandLine cmd = new CommandLine(new App()); - if (args.length == 0) { - cmd.usage(System.out); - return; - } - - Integer exitCode = cmd.execute(args); - System.exit(exitCode); - } - - public static void premain(String agentArgs, Instrumentation inst) { - System.err.println("AppMap: loaded"); - - String appmapPath = System.getProperty("appmap.config.file"); - if (appmapPath == null) { - appmapPath = DEFAULT_CONFIG_FILE; - } - - if (AppMapConfig.load(new File(appmapPath)) == null) { - System.err.printf("AppMap: failed to load config %s\n", appmapPath); - return; - } - - inst.addTransformer(new ClassFileTransformer()); - - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - public void run() { - RuntimeRecorder runtimeRecorder = RuntimeRecorder.get(); - if (runtimeRecorder.isEmpty()) { - return; - } - - runtimeRecorder.flushToFile("appmap.json"); - } - }, "Shutdown-thread")); - } -} \ No newline at end of file diff --git a/src/main/java/com/appland/appmap/commands/Inspect.java b/src/main/java/com/appland/appmap/commands/Inspect.java deleted file mode 100644 index 2d9c5066..00000000 --- a/src/main/java/com/appland/appmap/commands/Inspect.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.appland.appmap.commands; - -import com.appland.appmap.config.AppMapConfig; -import com.appland.appmap.config.AppMapPackage; - -// import com.appland.appmap.debugger.Trace; - -import java.io.File; -import java.io.FileNotFoundException; - -import java.util.ArrayList; -import java.util.concurrent.Callable; - -import javax.sound.midi.SysexMessage; - -import picocli.CommandLine.Command; -import picocli.CommandLine.Option; -import picocli.CommandLine.Parameters; - -@Command(name = "inspect", - description = "Inspect code and generate a classmap file") -public class Inspect implements Callable { - @Option(names = { "-o", "--output" }, - description = "Name of the output file (default: ${DEFAULT-VALUE})") - private File filename = new File("appmap.yml"); - - @Option(names = { "-c", "--class-path" }, - description = "Specifies a list of directories, JAR archives, and ZIP archives to search for class files. Class path entries are separated by colons (:).") - private String[] classPath; - - @Option(names = { "-j", "--jar" }, - description = "Name of the jar file to be invoked") - private File jarPath; - - @Option(names = { "-m", "--main" }, - description = "Name of the main class") - private String mainClass; - - @Option(names = { "-a", "--args" }, description = "launch arguments") - private String[] launchArgs = new String[]{}; - - @Override - public Void call() { - // AppMapConfig config = AppMapConfig.load(filename); - // if (config == null) { - // return null; - // } - - // Trace trace = new Trace(); - - // ArrayList classPaths = new ArrayList(); - // for (AppMapPackage p : config.packages) { - // trace.includeClassPath(p.path); - - // if (p.exclude == null) { - // continue; - // } - - // for (String exclusion : p.exclude) { - // trace.excludeClassPath(exclusion); - // } - // } - - // mainClass = String.format("%s", mainClass, String.join(" ", launchArgs)); - - // if (jarPath != null) { - // if (classPath != null && classPath.length > 0) { - // System.err.println("warn: both jar and class path options provided."); - // System.err.println(" only jar will be used."); - // } - // trace.execute(mainClass, jarPath); - // return null; - // } - - // if (mainClass == null || mainClass.isBlank()) { - // System.err.println("error: a jar path or main class must be provided"); - // return null; - // } - - // mainClass = String.format("%s", mainClass, String.join(" ", launchArgs)); - - // if (classPath != null && classPath.length > 0) { - // // if -c is provided many times, join them - // String joinedClassPath = String.join(":", classPath); - // trace.execute(mainClass, joinedClassPath); - // return null; - // } - - // trace.execute(mainClass); - - // // System.out.println(trace.serialize()); - - return null; - } -} \ No newline at end of file diff --git a/src/main/java/com/appland/appmap/commands/Record.java b/src/main/java/com/appland/appmap/commands/Record.java deleted file mode 100644 index 4674ff95..00000000 --- a/src/main/java/com/appland/appmap/commands/Record.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.appland.appmap.commands; - -import java.io.File; -import java.util.List; -import java.util.concurrent.Callable; - -import picocli.CommandLine.Command; -import picocli.CommandLine.Option; -import picocli.CommandLine.Parameters; - -@Command(name = "record", - description = "Record the execution of a program and generate an AppMap") -public class Record implements Callable { - @Option(names = { "-o", "--output" }, - description = "Name of the output file (default: ${DEFAULT-VALUE})") - private File filename = new File("appmap.json"); - - @Parameters(description = "Name of the executable archive to run and record") - private File executable; - - @Override - public Void call() { - return null; - } -} \ No newline at end of file diff --git a/src/main/java/com/appland/appmap/commands/Upload.java b/src/main/java/com/appland/appmap/commands/Upload.java deleted file mode 100644 index 1b46560b..00000000 --- a/src/main/java/com/appland/appmap/commands/Upload.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.appland.appmap.commands; - -import java.io.File; -import java.util.List; -import java.util.concurrent.Callable; - -import picocli.CommandLine.Command; -import picocli.CommandLine.Option; -import picocli.CommandLine.Parameters; - -@Command(name = "upload", - description = "Upload a scenario file to AppLand") -public class Upload implements Callable { - @Parameters(description = "Name of the file to upload") - private File filename; - - @Option(names = { "--open" }, - negatable = true, - description = "Whether to open the new scenario in the browser (default: ${DEFAULT-VALUE})") - private Boolean open = true; - - @Option(names = { "--owner" }, - description = "User id to own the scenario (default: ${DEFAULT-VALUE})") - private int owner = 1; - - @Option(names = { "--url" }, - description = "AppLand website URL (default: ${DEFAULT-VALUE})") - private String url = "https://appland-staging.herokuapp.com"; - - @Override - public Void call() { - return null; - } -} \ No newline at end of file diff --git a/src/main/java/com/appland/appmap/transform/HookableConfigPath.java b/src/main/java/com/appland/appmap/transform/HookableConfigPath.java index 749180ee..c0391b66 100644 --- a/src/main/java/com/appland/appmap/transform/HookableConfigPath.java +++ b/src/main/java/com/appland/appmap/transform/HookableConfigPath.java @@ -22,14 +22,6 @@ protected Boolean match(CtBehavior behavior) { } final String className = behavior.getDeclaringClass().getName(); - // if (className.contains("$")) { - // return false; - // } - - if (className.equals("com.appland.appmap.App")) { - System.out.println("xxx"); - } - return AppMapConfig.get().includes(className); } } diff --git a/src/main/java/com/appland/appmap/transform/HookableInterfaceName.java b/src/main/java/com/appland/appmap/transform/HookableInterfaceName.java index b565209e..e5fd1343 100644 --- a/src/main/java/com/appland/appmap/transform/HookableInterfaceName.java +++ b/src/main/java/com/appland/appmap/transform/HookableInterfaceName.java @@ -26,7 +26,10 @@ protected Boolean match(CtClass classType) { } } } catch (NotFoundException e) { - // fall through + // TODO: May want to handle this exception in a dedicated method, so we + // can be sure where the exception has occurred. + // + // In general, allow this exception to fall through. } return false; diff --git a/src/test/java/com/appland/appmap/AppTest.java b/src/test/java/com/appland/appmap/AppTest.java index 2061d91e..da709d0b 100644 --- a/src/test/java/com/appland/appmap/AppTest.java +++ b/src/test/java/com/appland/appmap/AppTest.java @@ -4,13 +4,11 @@ package com.appland.appmap; import org.junit.Test; -import static org.junit.Assert.*; -import com.appland.appmap.commands.Inspect; public class AppTest { @Test public void testAppHasAGreeting() { - Inspect classUnderTest = new Inspect(); - classUnderTest.call(); + SayHello classUnderTest = new SayHello(); + classUnderTest.hello(); } } diff --git a/src/test/java/com/appland/appmap/SayHello.java b/src/test/java/com/appland/appmap/SayHello.java new file mode 100644 index 00000000..84dcd9f1 --- /dev/null +++ b/src/test/java/com/appland/appmap/SayHello.java @@ -0,0 +1,7 @@ +package com.appland.appmap; + +public class SayHello { + public String hello() { + return "hello"; + } +}