import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1160164 - Run "all the tests" when invoking xpcshell's mach command with no arguments instead of passing "all" to the test resolver.;r=ahal DONTBUILD (5583e3bce)
- Bug 1171602 - Run mochitest using mach from a tests.zip, r=chmanchester (083d01f4c)
- Bug 1178850 - Generate naive method bindings in annotation processor; r=snorp (8f8a14e3d)
- Bug 1192079 - Support inner classes in generated JNI wrapper; r=snorp (1eefd6a4f)
This commit is contained in:
2022-01-07 09:26:54 +08:00
parent 99f10c4138
commit 640cd329f9
11 changed files with 321 additions and 81 deletions
@@ -15,17 +15,22 @@ import java.util.Arrays;
import java.util.Iterator;
public class AnnotationProcessor {
public static final String OUTFILE = "GeneratedJNIWrappers.cpp";
public static final String HEADERFILE = "GeneratedJNIWrappers.h";
public static final String SOURCE_FILE = "GeneratedJNIWrappers.cpp";
public static final String HEADER_FILE = "GeneratedJNIWrappers.h";
public static final String NATIVES_FILE = "GeneratedJNINatives.h";
public static final String GENERATED_COMMENT =
"// GENERATED CODE\n" +
"// Generated by the Java program at /build/annotationProcessors at compile time\n" +
"// from annotations on Java methods. To update, change the annotations on the\n" +
"// corresponding Javamethods and rerun the build. Manually updating this file\n" +
"// corresponding Java methods and rerun the build. Manually updating this file\n" +
"// will cause your build to fail.\n" +
"\n";
private static final StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
private static final StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
private static final StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT);
public static void main(String[] args) {
// We expect a list of jars on the commandline. If missing, whinge about it.
if (args.length <= 1) {
@@ -45,10 +50,9 @@ public class AnnotationProcessor {
// Get an iterator over the classes in the jar files given...
Iterator<ClassWithOptions> jarClassIterator = IterableJarLoadingURLClassLoader.getIteratorOverJars(args);
StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
headerFile.append(
"#ifndef GeneratedJNIWrappers_h__\n" +
"#define GeneratedJNIWrappers_h__\n" +
"#ifndef " + getHeaderGuardName(HEADER_FILE) + "\n" +
"#define " + getHeaderGuardName(HEADER_FILE) + "\n" +
"\n" +
"#include \"mozilla/jni/Refs.h\"\n" +
"\n" +
@@ -56,7 +60,6 @@ public class AnnotationProcessor {
"namespace widget {\n" +
"\n");
StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
implementationFile.append(
"#include \"GeneratedJNIWrappers.h\"\n" +
"#include \"mozilla/jni/Accessors.h\"\n" +
@@ -65,37 +68,19 @@ public class AnnotationProcessor {
"namespace widget {\n" +
"\n");
nativesFile.append(
"#ifndef " + getHeaderGuardName(NATIVES_FILE) + "\n" +
"#define " + getHeaderGuardName(NATIVES_FILE) + "\n" +
"\n" +
"#include \"GeneratedJNIWrappers.h\"\n" +
"#include \"mozilla/jni/Natives.h\"\n" +
"\n" +
"namespace mozilla {\n" +
"namespace widget {\n" +
"\n");
while (jarClassIterator.hasNext()) {
ClassWithOptions aClassTuple = jarClassIterator.next();
CodeGenerator generatorInstance;
// Get an iterator over the appropriately generated methods of this class
Iterator<AnnotatableEntity> methodIterator = new GeneratableElementIterator(aClassTuple.wrappedClass);
if (!methodIterator.hasNext()) {
continue;
}
generatorInstance = new CodeGenerator(aClassTuple);
// Iterate all annotated members in this class..
while (methodIterator.hasNext()) {
AnnotatableEntity aElementTuple = methodIterator.next();
switch (aElementTuple.mEntityType) {
case METHOD:
generatorInstance.generateMethod(aElementTuple);
break;
case FIELD:
generatorInstance.generateField(aElementTuple);
break;
case CONSTRUCTOR:
generatorInstance.generateConstructor(aElementTuple);
break;
}
}
headerFile.append(generatorInstance.getHeaderFileContents());
implementationFile.append(generatorInstance.getWrapperFileContents());
generateClass(jarClassIterator.next());
}
implementationFile.append(
@@ -107,38 +92,75 @@ public class AnnotationProcessor {
"\n" +
"} /* widget */\n" +
"} /* mozilla */\n" +
"#endif // GeneratedJNIWrappers_h__\n");
"#endif // " + getHeaderGuardName(HEADER_FILE) + "\n");
nativesFile.append(
"\n" +
"} /* widget */\n" +
"} /* mozilla */\n" +
"#endif // " + getHeaderGuardName(NATIVES_FILE) + "\n");
writeOutputFile(SOURCE_FILE, implementationFile);
writeOutputFile(HEADER_FILE, headerFile);
writeOutputFile(NATIVES_FILE, nativesFile);
writeOutputFiles(headerFile, implementationFile);
long e = System.currentTimeMillis();
System.out.println("Annotation processing complete in " + (e - s) + "ms");
}
private static void writeOutputFiles(StringBuilder aHeaderFile, StringBuilder aImplementationFile) {
FileOutputStream headerStream = null;
try {
headerStream = new FileOutputStream(OUTFILE);
headerStream.write(aImplementationFile.toString().getBytes());
} catch (IOException e) {
System.err.println("Unable to write " + OUTFILE + ". Perhaps a permissions issue?");
e.printStackTrace(System.err);
} finally {
if (headerStream != null) {
try {
headerStream.close();
} catch (IOException e) {
System.err.println("Unable to close headerStream due to "+e);
e.printStackTrace(System.err);
}
private static void generateClass(final ClassWithOptions annotatedClass) {
// Get an iterator over the appropriately generated methods of this class
final GeneratableElementIterator methodIterator
= new GeneratableElementIterator(annotatedClass);
final ClassWithOptions[] innerClasses = methodIterator.getInnerClasses();
if (!methodIterator.hasNext() && innerClasses.length == 0) {
return;
}
final CodeGenerator generatorInstance = new CodeGenerator(annotatedClass);
generatorInstance.generateClasses(innerClasses);
// Iterate all annotated members in this class..
while (methodIterator.hasNext()) {
AnnotatableEntity aElementTuple = methodIterator.next();
switch (aElementTuple.mEntityType) {
case METHOD:
generatorInstance.generateMethod(aElementTuple);
break;
case NATIVE:
generatorInstance.generateNative(aElementTuple);
break;
case FIELD:
generatorInstance.generateField(aElementTuple);
break;
case CONSTRUCTOR:
generatorInstance.generateConstructor(aElementTuple);
break;
}
}
headerFile.append(generatorInstance.getHeaderFileContents());
implementationFile.append(generatorInstance.getWrapperFileContents());
nativesFile.append(generatorInstance.getNativesFileContents());
for (ClassWithOptions innerClass : innerClasses) {
generateClass(innerClass);
}
}
private static String getHeaderGuardName(final String name) {
return name.replaceAll("\\W", "_");
}
private static void writeOutputFile(final String name,
final StringBuilder content) {
FileOutputStream outStream = null;
try {
outStream = new FileOutputStream(HEADERFILE);
outStream.write(aHeaderFile.toString().getBytes());
outStream = new FileOutputStream(name);
outStream.write(content.toString().getBytes());
} catch (IOException e) {
System.err.println("Unable to write " + HEADERFILE + ". Perhaps a permissions issue?");
System.err.println("Unable to write " + name + ". Perhaps a permissions issue?");
e.printStackTrace(System.err);
} finally {
if (outStream != null) {
+96 -10
View File
@@ -22,6 +22,8 @@ public class CodeGenerator {
// Buffers holding the strings to ultimately be written to the output files.
private final StringBuilder cpp = new StringBuilder();
private final StringBuilder header = new StringBuilder();
private final StringBuilder natives = new StringBuilder();
private final StringBuilder nativesInits = new StringBuilder();
private final Class<?> cls;
private final String clsName;
@@ -33,8 +35,8 @@ public class CodeGenerator {
this.clsName = annotatedClass.generatedName;
header.append(
"class " + clsName + " : public mozilla::jni::Class<" + clsName + "> {\n" +
"\n" +
"class " + clsName + " : public mozilla::jni::Class<" + clsName + ">\n" +
"{\n" +
"public:\n" +
" typedef mozilla::jni::Ref<" + clsName + "> Ref;\n" +
" typedef mozilla::jni::LocalRef<" + clsName + "> LocalRef;\n" +
@@ -51,6 +53,12 @@ public class CodeGenerator {
cpp.append(
"constexpr char " + clsName + "::name[];\n" +
"\n");
natives.append(
"template<class Impl>\n" +
"class " + clsName + "::Natives : " +
"public mozilla::jni::NativeImpl<" + clsName + ", Impl>\n" +
"{\n");
}
private String getTraitsName(String uniqueName, boolean includeScope) {
@@ -72,20 +80,30 @@ public class CodeGenerator {
}
private void generateMember(AnnotationInfo info, Member member,
String uniqueName, Class<?> type) {
String uniqueName, Class<?> type, Class<?>[] argTypes) {
final StringBuilder args = new StringBuilder();
for (Class<?> argType : argTypes) {
args.append("\n " + getNativeParameterType(argType, info) + ",");
}
if (args.length() > 0) {
args.setLength(args.length() - 1);
}
header.append(
"public:\n" +
" struct " + getTraitsName(uniqueName, /* includeScope */ false) + " {\n" +
" typedef " + clsName + " Owner;\n" +
" typedef " + getNativeReturnType(type, info) + " ReturnType;\n" +
" typedef " + getNativeParameterType(type, info) + " SetterType;\n" +
" typedef mozilla::jni::Args<" + args + "> Args;\n" +
" static constexpr char name[] = \"" +
Utils.getMemberName(member) + "\";\n" +
" static constexpr char signature[] =\n" +
" \"" + Utils.getSignature(member) + "\";\n" +
" static const bool isStatic = " + Utils.isStatic(member) + ";\n" +
" static const bool isMultithreaded = " + info.isMultithreaded + ";\n" +
" static const mozilla::jni::ExceptionMode exceptionMode = " + (
" static const mozilla::jni::ExceptionMode exceptionMode =\n" +
" " + (
info.catchException ? "mozilla::jni::ExceptionMode::NSRESULT" :
info.noThrow ? "mozilla::jni::ExceptionMode::IGNORE" :
"mozilla::jni::ExceptionMode::ABORT") + ";\n" +
@@ -248,15 +266,15 @@ public class CodeGenerator {
final Method method = annotatedMethod.getMethod();
final AnnotationInfo info = annotatedMethod.mAnnotationInfo;
final String uniqueName = getUniqueMethodName(info.wrapperName);
final Class<?>[] argTypes = method.getParameterTypes();
final Class<?> returnType = method.getReturnType();
if (method.isSynthetic()) {
return;
}
generateMember(info, method, uniqueName, returnType);
generateMember(info, method, uniqueName, returnType, argTypes);
final Class<?>[] argTypes = method.getParameterTypes();
final boolean isStatic = Utils.isStatic(method);
header.append(
@@ -272,6 +290,35 @@ public class CodeGenerator {
"\n");
}
/**
* Append the appropriate generated code to the buffers for the native method provided.
*
* @param annotatedMethod The Java native method, plus annotation data.
*/
public void generateNative(AnnotatableEntity annotatedMethod) {
// Unpack the tuple and extract some useful fields from the Method..
final Method method = annotatedMethod.getMethod();
final AnnotationInfo info = annotatedMethod.mAnnotationInfo;
final String uniqueName = getUniqueMethodName(info.wrapperName);
final Class<?>[] argTypes = method.getParameterTypes();
final Class<?> returnType = method.getReturnType();
generateMember(info, method, uniqueName, returnType, argTypes);
final String traits = getTraitsName(uniqueName, /* includeScope */ true);
if (nativesInits.length() > 0) {
nativesInits.append(',');
}
nativesInits.append(
"\n" +
"\n" +
" mozilla::jni::MakeNativeMethod<" + traits + ">(\n" +
" mozilla::jni::NativeStub<" + traits + ", Impl>\n" +
" ::template Wrap<&Impl::" + info.wrapperName + ">)");
}
private String getLiteral(Object val, AnnotationInfo info) {
final Class<?> type = val.getClass();
@@ -348,7 +395,7 @@ public class CodeGenerator {
// Fall back to using accessors if we encounter an exception.
}
generateMember(info, field, uniqueName, type);
generateMember(info, field, uniqueName, type, EMPTY_CLASS_ARRAY);
final Class<?>[] getterArgs = EMPTY_CLASS_ARRAY;
@@ -389,15 +436,14 @@ public class CodeGenerator {
final AnnotationInfo info = annotatedConstructor.mAnnotationInfo;
final String wrapperName = "New";
final String uniqueName = getUniqueMethodName(wrapperName);
final Class<?>[] argTypes = method.getParameterTypes();
final Class<?> returnType = cls;
if (method.isSynthetic()) {
return;
}
generateMember(info, method, uniqueName, returnType);
final Class<?>[] argTypes = method.getParameterTypes();
generateMember(info, method, uniqueName, returnType, argTypes);
header.append(
" " + generateDeclaration(wrapperName, argTypes,
@@ -439,6 +485,21 @@ public class CodeGenerator {
}
}
public void generateClasses(final ClassWithOptions[] classes) {
if (classes.length == 0) {
return;
}
header.append(
"public:\n");
for (final ClassWithOptions cls : classes) {
// Extract "Inner" from "Outer::Inner".
header.append(
" class " + Utils.getUnqualifiedName(cls.generatedName) + ";\n");
}
header.append('\n');
}
/**
* Get the finalised bytes to go into the generated wrappers file.
*
@@ -454,9 +515,34 @@ public class CodeGenerator {
* @return The bytes to be written to the header file.
*/
public String getHeaderFileContents() {
if (nativesInits.length() > 0) {
header.append(
"public:\n" +
" template<class Impl> class Natives;\n");
}
header.append(
"};\n" +
"\n");
return header.toString();
}
/**
* Get the finalised bytes to go into the generated natives header file.
*
* @return The bytes to be written to the header file.
*/
public String getNativesFileContents() {
if (nativesInits.length() == 0) {
return "";
}
natives.append(
"public:\n" +
" static constexpr JNINativeMethod methods[] = {" + nativesInits + '\n' +
" };\n" +
"};\n" +
"\n" +
"template<class Impl>\n" +
"constexpr JNINativeMethod " + clsName + "::Natives<Impl>::methods[];\n");
return natives.toString();
}
}
@@ -10,13 +10,14 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Union type to hold either a method, field, or ctor. Allows us to iterate "The generatable stuff", despite
* the fact that such things can be of either flavour.
*/
public class AnnotatableEntity {
public enum ENTITY_TYPE {METHOD, FIELD, CONSTRUCTOR}
public enum ENTITY_TYPE {METHOD, NATIVE, FIELD, CONSTRUCTOR}
private final Member mMember;
public final ENTITY_TYPE mEntityType;
@@ -28,7 +29,11 @@ public class AnnotatableEntity {
mAnnotationInfo = aAnnotationInfo;
if (aObject instanceof Method) {
mEntityType = ENTITY_TYPE.METHOD;
if (Modifier.isNative(aObject.getModifiers())) {
mEntityType = ENTITY_TYPE.NATIVE;
} else {
mEntityType = ENTITY_TYPE.METHOD;
}
} else if (aObject instanceof Field) {
mEntityType = ENTITY_TYPE.FIELD;
} else {
@@ -37,7 +42,7 @@ public class AnnotatableEntity {
}
public Method getMethod() {
if (mEntityType != ENTITY_TYPE.METHOD) {
if (mEntityType != ENTITY_TYPE.METHOD && mEntityType != ENTITY_TYPE.NATIVE) {
throw new UnsupportedOperationException("Attempt to cast to unsupported member type.");
}
return (Method) mMember;
@@ -28,20 +28,21 @@ public class JarClassIterator implements Iterator<ClassWithOptions> {
String className = mTargetClassListIterator.next();
try {
Class<?> ret = mTarget.loadClass(className);
final String canonicalName;
// Incremental builds can leave stale classfiles in the jar. Such classfiles will cause
// an exception at this point. We can safely ignore these classes - they cannot possibly
// ever be loaded as they conflict with their parent class and will be killed by Proguard
// later on anyway.
// ever be loaded as they conflict with their parent class and will be killed by
// Proguard later on anyway.
final Class<?> enclosingClass;
try {
canonicalName = ret.getCanonicalName();
enclosingClass = ret.getEnclosingClass();
} catch (IncompatibleClassChangeError e) {
return next();
}
if (canonicalName == null || "null".equals(canonicalName)) {
if (enclosingClass != null) {
// Anonymous inner class - unsupported.
// Or named inner class, which will be processed when we process the outer class.
return next();
}
@@ -6,6 +6,7 @@ package org.mozilla.gecko.annotationProcessors.utils;
import org.mozilla.gecko.annotationProcessors.AnnotationInfo;
import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
@@ -13,6 +14,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
/**
@@ -21,13 +23,17 @@ import java.util.Iterator;
* parameters) and the argument.
*/
public class GeneratableElementIterator implements Iterator<AnnotatableEntity> {
private final ClassWithOptions mClass;
private final Member[] mObjects;
private AnnotatableEntity mNextReturnValue;
private int mElementIndex;
private boolean mIterateEveryEntry;
public GeneratableElementIterator(Class<?> aClass) {
public GeneratableElementIterator(ClassWithOptions annotatedClass) {
mClass = annotatedClass;
final Class<?> aClass = annotatedClass.wrappedClass;
// Get all the elements of this class as AccessibleObjects.
Member[] aMethods = aClass.getDeclaredMethods();
Member[] aFields = aClass.getDeclaredFields();
@@ -59,6 +65,56 @@ public class GeneratableElementIterator implements Iterator<AnnotatableEntity> {
findNextValue();
}
private Class<?>[] getFilteredInnerClasses() {
// Go through all inner classes and see which ones we want to generate.
final Class<?>[] candidates = mClass.wrappedClass.getDeclaredClasses();
int count = 0;
for (int i = 0; i < candidates.length; ++i) {
final GeneratableElementIterator testIterator
= new GeneratableElementIterator(new ClassWithOptions(candidates[i], null));
if (testIterator.hasNext()
|| testIterator.getFilteredInnerClasses() != null) {
count++;
continue;
}
// Clear out ones that don't match.
candidates[i] = null;
}
return count > 0 ? candidates : null;
}
public ClassWithOptions[] getInnerClasses() {
final Class<?>[] candidates = getFilteredInnerClasses();
if (candidates == null) {
return new ClassWithOptions[0];
}
int count = 0;
for (Class<?> candidate : candidates) {
if (candidate != null) {
count++;
}
}
final ClassWithOptions[] ret = new ClassWithOptions[count];
count = 0;
for (Class<?> candidate : candidates) {
if (candidate != null) {
ret[count++] = new ClassWithOptions(
candidate, mClass.generatedName + "::" + candidate.getSimpleName());
}
}
assert ret.length == count;
Arrays.sort(ret, new Comparator<ClassWithOptions>() {
@Override public int compare(ClassWithOptions lhs, ClassWithOptions rhs) {
return lhs.generatedName.compareTo(rhs.generatedName);
}
});
return ret;
}
/**
* Find and cache the next appropriately annotated method, plus the annotation parameter, if
* one exists. Otherwise cache null, so hasNext returns false.
@@ -225,6 +225,10 @@ public class Utils {
return member.getName();
}
public static String getUnqualifiedName(String name) {
return name.substring(name.lastIndexOf(':') + 1);
}
/**
* Determine if a member is declared static.
*
@@ -0,0 +1,41 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import unicode_literals
from argparse import Namespace
import os
from mach.decorators import (
CommandProvider,
Command,
)
def run_mochitest(context, **kwargs):
args = Namespace(**kwargs)
args.certPath = context.certs_dir
args.utilityPath = context.bin_dir
args.extraProfileFiles.append(os.path.join(context.bin_dir, 'plugins'))
from runtests import run_test_harness
return run_test_harness(args)
def setup_argument_parser():
from mochitest_options import MochitestArgumentParser
return MochitestArgumentParser(app='generic')
@CommandProvider
class MochitestCommands(object):
def __init__(self, context):
self.context = context
@Command('mochitest', category='testing',
description='Run the mochitest harness.',
parser=setup_argument_parser)
def mochitest(self, **kwargs):
return run_mochitest(self.context, **kwargs)
+6 -3
View File
@@ -70,7 +70,7 @@ class MochitestArguments(ArgumentContainer):
"help": "Override the default binary used to run tests with the path provided, e.g "
"/usr/bin/firefox. If you have run ./mach package beforehand, you can "
"specify 'dist' to run tests against the distribution bundle's binary.",
"suppress": True,
"suppress": build_obj is not None,
}],
[["--utility-path"],
{"dest": "utilityPath",
@@ -582,7 +582,9 @@ class MochitestArguments(ArgumentContainer):
"could not find xre directory, --xre-path must be specified")
# allow relative paths
options.xrePath = self.get_full_path(options.xrePath, parser.oldcwd)
if options.xrePath:
options.xrePath = self.get_full_path(options.xrePath, parser.oldcwd)
if options.profilePath:
options.profilePath = self.get_full_path(options.profilePath, parser.oldcwd)
@@ -1140,7 +1142,8 @@ container_map = {
class MochitestArgumentParser(ArgumentParser):
"""
Usage instructions for runtests.py.
Usage instructions for Mochitest.
All arguments are optional.
If --chrome is specified, chrome tests will be run instead of web content tests.
If --browser-chrome is specified, browser-chrome tests will be run instead of web content tests.
+1
View File
@@ -62,6 +62,7 @@ TEST_HARNESS_FILES.testing.mochitest += [
'jetpack-addon-overlay.xul',
'jetpack-package-harness.js',
'jetpack-package-overlay.xul',
'mach_test_package_commands.py',
'manifest.webapp',
'manifestLibrary.js',
'mochitest_options.py',
@@ -11,11 +11,28 @@ import time
SEARCH_PATHS = [
'mochitest',
'mozbase/mozcrash',
'mozbase/mozdebug',
'mozbase/mozdevice',
'mozbase/mozfile',
'mozbase/mozhttpd',
'mozbase/mozlog',
'mozbase/moznetwork',
'mozbase/mozprocess',
'mozbase/mozprofile',
'mozbase/mozrunner',
'mozbase/mozsystemmonitor',
'mozbase/mozinfo',
'mozbase/moztest',
'mozbase/mozversion',
'mozbase/manifestparser',
'tools/mach',
]
# Individual files providing mach commands.
MACH_MODULES = [
'mochitest/mach_test_package_commands.py',
'tools/mach/mach/commands/commandinfo.py',
]
@@ -60,6 +77,10 @@ def bootstrap(test_package_root):
import mach.main
def populate_context(context, key=None):
context.package_root = test_package_root
context.certs_dir = os.path.join(test_package_root, 'certs')
context.bin_dir = os.path.join(test_package_root, 'bin')
context.modules_dir = os.path.join(test_package_root, 'modules')
return context
mach = mach.main.Mach(os.getcwd())
+1 -1
View File
@@ -82,7 +82,7 @@ class XPCShellRunner(MozbuildObject):
if not os.path.isfile(os.path.join(self.topsrcdir, 'build', 'automationutils.py')):
sys.path.append(os.path.join(self.topsrcdir, 'mozilla', 'build'))
if test_paths == ['all']:
if test_paths == 'all':
self.run_suite(interactive=interactive,
keep_going=keep_going, shuffle=shuffle, sequential=sequential,
debugger=debugger, debuggerArgs=debuggerArgs,