diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ef663e64..457aad0d 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 cf6d6f45..75b8c7c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Wed Aug 08 12:17:32 PDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-all.zip diff --git a/gradlew b/gradlew index cccdd3d5..af6708ff 100755 --- a/gradlew +++ b/gradlew @@ -28,7 +28,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/gradlew.bat b/gradlew.bat index e95643d6..0f8d5937 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/settings.gradle b/settings.gradle index a0258112..65480879 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ def modules = [ 'transportable-udfs-codegen', 'transportable-udfs-compile-utils', 'transportable-udfs-hive', + 'transportable-udfs-plugin', 'transportable-udfs-presto', 'transportable-udfs-spark', 'transportable-udfs-test:transportable-udfs-test-api', diff --git a/transportable-udfs-annotation-processor/src/main/java/com/linkedin/transport/processor/Constants.java b/transportable-udfs-annotation-processor/src/main/java/com/linkedin/transport/processor/Constants.java index 3ebf6ae5..2c308ffb 100644 --- a/transportable-udfs-annotation-processor/src/main/java/com/linkedin/transport/processor/Constants.java +++ b/transportable-udfs-annotation-processor/src/main/java/com/linkedin/transport/processor/Constants.java @@ -9,7 +9,7 @@ public class Constants { - public static final String UDF_RESOURCE_FILE_PATH = "META-INF/transport-udfs/udf-properties.json"; + public static final String UDF_RESOURCE_FILE_PATH = "META-INF/transport-udfs/metadata.json"; public static final String INTERFACE_NOT_IMPLEMENTED_ERROR = String.format( diff --git a/transportable-udfs-annotation-processor/src/main/java/com/linkedin/transport/processor/TransportProcessor.java b/transportable-udfs-annotation-processor/src/main/java/com/linkedin/transport/processor/TransportProcessor.java index 352df6b9..12e25002 100644 --- a/transportable-udfs-annotation-processor/src/main/java/com/linkedin/transport/processor/TransportProcessor.java +++ b/transportable-udfs-annotation-processor/src/main/java/com/linkedin/transport/processor/TransportProcessor.java @@ -91,7 +91,7 @@ public boolean process(Set annotations, RoundEnvironment private void processImpl(RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { - generateUDFPropertiesFile(); + generateUDFMetadataFile(); } else { processElements(roundEnv.getRootElements()); } @@ -178,18 +178,18 @@ private boolean typeElementOverridesTopLevelStdUDFMethods(TypeElement typeElemen } /** - * Generates the UDF properties resource file in a pretty-printed JSON format + * Generates the UDF metadata resource file in a pretty-printed JSON format */ - private void generateUDFPropertiesFile() { + private void generateUDFMetadataFile() { Filer filer = processingEnv.getFiler(); try { FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", Constants.UDF_RESOURCE_FILE_PATH); try (Writer writer = fileObject.openWriter()) { _transportUdfMetadata.toJson(writer); } - debug("Wrote Transport UDF properties file to: " + fileObject.toUri()); + debug("Wrote Transport UDF metadata file to: " + fileObject.toUri()); } catch (IOException e) { - fatalError(String.format("Unable to create UDF properties resource file: %s", e)); + fatalError(String.format("Unable to create UDF metadata resource file: %s", e)); } } diff --git a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/AbstractTestWrapperGenerator.java b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/AbstractTestWrapperGenerator.java index de3387ec..5f73e16a 100644 --- a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/AbstractTestWrapperGenerator.java +++ b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/AbstractTestWrapperGenerator.java @@ -44,38 +44,30 @@ void teardown() { _resourcesOutputDir.delete(); } - void testWrapperGenerator(String udfPropertiesFileResource, String expectedSourcesOutputFolderResource) { - testWrapperGenerator(udfPropertiesFileResource, expectedSourcesOutputFolderResource, null); + void testWrapperGenerator(String udfMetadataFileResource, String expectedSourcesOutputFolderResource) { + testWrapperGenerator(udfMetadataFileResource, expectedSourcesOutputFolderResource, null); } - void testWrapperGenerator(String udfPropertiesFileResource, String expectedSourcesOutputFolderResource, + void testWrapperGenerator(String udfMetadataFileResource, String expectedSourcesOutputFolderResource, String expectedResourcesOutputFolderResource) { WrapperGeneratorContext context = - new WrapperGeneratorContext(TestUtils.getUDFPropertiesFromResource(udfPropertiesFileResource), + new WrapperGeneratorContext(TestUtils.getUDFMetadataFromResource(udfMetadataFileResource), _sourcesOutputDir, _resourcesOutputDir); getWrapperGenerator().generateWrappers(context); - final Path expectedSourcesOutputPath; - final Path expectedResourcesOutputPath; try { - expectedSourcesOutputPath = Paths.get( + Path expectedSourcesOutputPath = Paths.get( Thread.currentThread().getContextClassLoader().getResource(expectedSourcesOutputFolderResource).toURI()); + TestUtils.assertDirectoriesAreEqual(_sourcesOutputDir.toPath(), expectedSourcesOutputPath); if (expectedResourcesOutputFolderResource != null) { - expectedResourcesOutputPath = Paths.get( + Path expectedResourcesOutputPath = Paths.get( Thread.currentThread().getContextClassLoader().getResource(expectedResourcesOutputFolderResource).toURI()); - } else { - // No resources directory specified, so compare to an empty dir - File tmpEmptyResourcesDir = Files.createTempDir(); - tmpEmptyResourcesDir.deleteOnExit(); - expectedResourcesOutputPath = tmpEmptyResourcesDir.toPath(); + TestUtils.assertDirectoriesAreEqual(_resourcesOutputDir.toPath(), expectedResourcesOutputPath); } } catch (URISyntaxException e) { throw new RuntimeException("Error constructing URI", e); } - - TestUtils.assertDirectoriesAreEqual(_sourcesOutputDir.toPath(), expectedSourcesOutputPath); - TestUtils.assertDirectoriesAreEqual(_resourcesOutputDir.toPath(), expectedResourcesOutputPath); } abstract WrapperGenerator getWrapperGenerator(); diff --git a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestHiveWrapperGenerator.java b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestHiveWrapperGenerator.java index 6d659af3..2416ae7d 100644 --- a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestHiveWrapperGenerator.java +++ b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestHiveWrapperGenerator.java @@ -17,6 +17,6 @@ WrapperGenerator getWrapperGenerator() { @Test public void testHiveWrapperGenerator() { - testWrapperGenerator("inputs/sample-udf-properties.json", "outputs/sample-udf-properties/hive/sources"); + testWrapperGenerator("inputs/sample-udf-metadata.json", "outputs/sample-udf-metadata/hive/sources"); } } diff --git a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestPrestoWrapperGenerator.java b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestPrestoWrapperGenerator.java index 5316b398..3c2fafbf 100644 --- a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestPrestoWrapperGenerator.java +++ b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestPrestoWrapperGenerator.java @@ -17,7 +17,7 @@ WrapperGenerator getWrapperGenerator() { @Test public void testPrestoWrapperGenerator() { - testWrapperGenerator("inputs/sample-udf-properties.json", "outputs/sample-udf-properties/presto/sources", - "outputs/sample-udf-properties/presto/resources"); + testWrapperGenerator("inputs/sample-udf-metadata.json", "outputs/sample-udf-metadata/presto/sources", + "outputs/sample-udf-metadata/presto/resources"); } } diff --git a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestSparkWrapperGenerator.java b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestSparkWrapperGenerator.java index 8627a3cf..02bc0d6a 100644 --- a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestSparkWrapperGenerator.java +++ b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestSparkWrapperGenerator.java @@ -17,6 +17,6 @@ WrapperGenerator getWrapperGenerator() { @Test public void testSparkWrapperGenerator() { - testWrapperGenerator("inputs/sample-udf-properties.json", "outputs/sample-udf-properties/spark/sources"); + testWrapperGenerator("inputs/sample-udf-metadata.json", "outputs/sample-udf-metadata/spark/sources"); } } diff --git a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestUtils.java b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestUtils.java index 1a41ae03..0879b4c3 100644 --- a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestUtils.java +++ b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestUtils.java @@ -21,12 +21,12 @@ class TestUtils { private TestUtils() { } - static TransportUDFMetadata getUDFPropertiesFromResource(String resource) { + static TransportUDFMetadata getUDFMetadataFromResource(String resource) { try (InputStreamReader reader = new InputStreamReader( Thread.currentThread().getContextClassLoader().getResourceAsStream(resource))) { return TransportUDFMetadata.fromJson(reader); } catch (IOException e) { - throw new RuntimeException("Could not read UDF properties from resource: " + resource, e); + throw new RuntimeException("Could not read UDF metadata from resource: " + resource, e); } } diff --git a/transportable-udfs-codegen/src/test/resources/inputs/sample-udf-properties.json b/transportable-udfs-codegen/src/test/resources/inputs/sample-udf-metadata.json similarity index 100% rename from transportable-udfs-codegen/src/test/resources/inputs/sample-udf-properties.json rename to transportable-udfs-codegen/src/test/resources/inputs/sample-udf-metadata.json diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/hive/sources/udfs/hive/OverloadedUDF.java b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/hive/sources/udfs/hive/OverloadedUDF.java similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/hive/sources/udfs/hive/OverloadedUDF.java rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/hive/sources/udfs/hive/OverloadedUDF.java diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/hive/sources/udfs/hive/SimpleUDF.java b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/hive/sources/udfs/hive/SimpleUDF.java similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/hive/sources/udfs/hive/SimpleUDF.java rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/hive/sources/udfs/hive/SimpleUDF.java diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/resources/META-INF/services/com.facebook.presto.metadata.SqlScalarFunction b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/resources/META-INF/services/com.facebook.presto.metadata.SqlScalarFunction similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/resources/META-INF/services/com.facebook.presto.metadata.SqlScalarFunction rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/resources/META-INF/services/com.facebook.presto.metadata.SqlScalarFunction diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/OverloadedUDFInt.java b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/OverloadedUDFInt.java similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/OverloadedUDFInt.java rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/OverloadedUDFInt.java diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/OverloadedUDFString.java b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/OverloadedUDFString.java similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/OverloadedUDFString.java rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/OverloadedUDFString.java diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/SimpleUDF.java b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/SimpleUDF.java similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/SimpleUDF.java rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/SimpleUDF.java diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/spark/sources/udfs/spark/OverloadedUDF.scala b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/spark/sources/udfs/spark/OverloadedUDF.scala similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/spark/sources/udfs/spark/OverloadedUDF.scala rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/spark/sources/udfs/spark/OverloadedUDF.scala diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/spark/sources/udfs/spark/SimpleUDF.scala b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/spark/sources/udfs/spark/SimpleUDF.scala similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/spark/sources/udfs/spark/SimpleUDF.scala rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/spark/sources/udfs/spark/SimpleUDF.scala diff --git a/transportable-udfs-examples/gradlew b/transportable-udfs-examples/gradlew index cccdd3d5..af6708ff 100755 --- a/transportable-udfs-examples/gradlew +++ b/transportable-udfs-examples/gradlew @@ -28,7 +28,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/transportable-udfs-examples/gradlew.bat b/transportable-udfs-examples/gradlew.bat index e95643d6..0f8d5937 100644 --- a/transportable-udfs-examples/gradlew.bat +++ b/transportable-udfs-examples/gradlew.bat @@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle b/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle index d545b24a..c158042e 100644 --- a/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle +++ b/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle @@ -1,67 +1,31 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath('com.linkedin.transport:transportable-udfs-plugin') + } +} + apply plugin: 'java' +apply plugin: 'com.linkedin.transport.plugin' dependencies { - compile('com.linkedin.transport:transportable-udfs-api') // TODO: Reference all external dependencies from a single gradle file compile('com.google.guava:guava:24.1-jre') compile('org.apache.commons:commons-io:1.3.2') - testCompile('com.linkedin.transport:transportable-udfs-test-api') } -// ============================================================================ -// transportable-udfs-plugin cannot be applied to this module as the plugin is also -// built in this project. In projects using the Transportable UDF framework, the -// following code will be applied by the plugin -// ============================================================================ - -dependencies { - annotationProcessor('com.linkedin.transport:transportable-udfs-annotation-processor') +// If the license plugin is applied, disable license checks for the autogenerated source sets +plugins.withId('com.github.hierynomus.license') { + licenseHive.enabled = false + licensePresto.enabled = false + licenseSpark.enabled = false } // TODO: Add a debugPlatform flag to allow debugging specific test methods in IntelliJ // for a particular platform other than default -configurations { - platformTestRuntime { extendsFrom testRuntime } - hiveTestRuntime { extendsFrom platformTestRuntime } - prestoTestRuntime { extendsFrom platformTestRuntime } - sparkTestRuntime { extendsFrom platformTestRuntime } -} - -dependencies { - testRuntimeOnly('com.linkedin.transport:transportable-udfs-test-generic') - platformTestRuntime sourceSets.main.output, sourceSets.test.output - hiveTestRuntime('com.linkedin.transport:transportable-udfs-test-hive') - prestoTestRuntime('com.linkedin.transport:transportable-udfs-test-presto') - sparkTestRuntime('com.linkedin.transport:transportable-udfs-test-spark') -} - -task hiveTest(type: Test, dependsOn: test) { - group 'Verification' - description 'Runs the Hive tests.' - testClassesDirs = sourceSets.test.output.classesDirs - classpath = configurations.hiveTestRuntime - useTestNG() -} - -task prestoTest(type: Test, dependsOn: test) { - group 'Verification' - description 'Runs the Presto tests.' - testClassesDirs = sourceSets.test.output.classesDirs - classpath = configurations.prestoTestRuntime - useTestNG() -} - -task sparkTest(type: Test, dependsOn: test) { - group 'Verification' - description 'Runs the Spark tests.' - testClassesDirs = sourceSets.test.output.classesDirs - classpath = configurations.sparkTestRuntime - useTestNG() -} - -check.dependsOn(hiveTest, prestoTest, sparkTest) - prestoTest { exclude '**/TestArrayFillFunctionFailsOnPresto.class' exclude '**/TestStructCreateByIndexFunctionFailsOnPresto.class' diff --git a/transportable-udfs-plugin/build.gradle b/transportable-udfs-plugin/build.gradle new file mode 100644 index 00000000..30f61b85 --- /dev/null +++ b/transportable-udfs-plugin/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'java' + id 'java-gradle-plugin' +} + +gradlePlugin { + plugins { + simplePlugin { + id = 'com.linkedin.transport.plugin' + implementationClass = 'com.linkedin.transport.plugin.TransportPlugin' + } + } +} + +dependencies { + compile project(':transportable-udfs-api') + compile project(':transportable-udfs-codegen') + compile ('com.google.guava:guava:24.1-jre') + compile ('com.google.code.gson:gson:2.8.5') + testCompile('org.spockframework:spock-core:1.1-groovy-2.4') { + exclude group: 'org.codehaus.groovy' + } +} + +def writeVersionInfo = { file -> + ant.propertyfile(file: file) { + entry(key: "transport-version", value: version) + entry(key: "hive-version", value: '1.2.2') + entry(key: "presto-version", value: '0.203') + entry(key: "spark-version", value: '2.3.0') + } +} + +processResources.doLast { + writeVersionInfo(new File(sourceSets.main.output.resourcesDir, "version-info.properties")) +} \ No newline at end of file diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/ConfigurationType.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/ConfigurationType.java new file mode 100644 index 00000000..492abf32 --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/ConfigurationType.java @@ -0,0 +1,13 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +public enum ConfigurationType { + ANNOTATION_PROCESSOR, + COMPILE_ONLY, + IMPLEMENTATION, + RUNTIME_ONLY +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java new file mode 100644 index 00000000..e6ac637d --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java @@ -0,0 +1,102 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +import com.google.common.collect.ImmutableList; +import com.linkedin.transport.codegen.HiveWrapperGenerator; +import com.linkedin.transport.codegen.PrestoWrapperGenerator; +import com.linkedin.transport.codegen.SparkWrapperGenerator; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Properties; + +import static com.linkedin.transport.plugin.ConfigurationType.*; + + +/** + * Stores default configurations for the Transport UDF plugin + */ +class Defaults { + + private Defaults() { + } + + // The versions of the Transport and supported platforms to apply corresponding versions of the platform dependencies + private static final Properties DEFAULT_VERSIONS = loadDefaultVersions(); + + private static Properties loadDefaultVersions() { + try (InputStream is = + Thread.currentThread().getContextClassLoader().getResourceAsStream("version-info.properties")) { + Properties defaultVersions = new Properties(); + defaultVersions.load(is); + return defaultVersions; + } catch (IOException e) { + throw new RuntimeException("Error loading version-info.properties", e); + } + } + + static final List MAIN_SOURCE_SET_DEPENDENCY_CONFIGURATIONS = ImmutableList.of( + getDependencyConfiguration(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-api", "transport"), + getDependencyConfiguration(ANNOTATION_PROCESSOR, "com.linkedin.transport:transportable-udfs-annotation-processor", + "transport") + ); + + static final List TEST_SOURCE_SET_DEPENDENCY_CONFIGURATIONS = ImmutableList.of( + getDependencyConfiguration(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-test-api", "transport"), + getDependencyConfiguration(RUNTIME_ONLY, "com.linkedin.transport:transportable-udfs-test-generic", "transport") + ); + + static final List DEFAULT_PLATFORMS = ImmutableList.of( + new Platform( + "presto", + Language.JAVA, + PrestoWrapperGenerator.class, + ImmutableList.of( + getDependencyConfiguration(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-presto", + "transport"), + getDependencyConfiguration(COMPILE_ONLY, "com.facebook.presto:presto-main", "presto") + ), + ImmutableList.of( + getDependencyConfiguration(RUNTIME_ONLY, "com.linkedin.transport:transportable-udfs-test-presto", + "transport") + ) + ), + new Platform( + "hive", + Language.JAVA, + HiveWrapperGenerator.class, + ImmutableList.of( + getDependencyConfiguration(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-hive", "transport"), + getDependencyConfiguration(COMPILE_ONLY, "org.apache.hive:hive-exec", "hive") + ), + ImmutableList.of( + getDependencyConfiguration(RUNTIME_ONLY, "com.linkedin.transport:transportable-udfs-test-hive", + "transport") + ) + ), + new Platform( + "spark", + Language.SCALA, + SparkWrapperGenerator.class, + ImmutableList.of( + getDependencyConfiguration(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-spark", + "transport"), + getDependencyConfiguration(COMPILE_ONLY, "org.apache.spark:spark-sql_2.11", "spark") + ), + ImmutableList.of( + getDependencyConfiguration(RUNTIME_ONLY, "com.linkedin.transport:transportable-udfs-test-spark", + "transport") + ) + ) + ); + + private static DependencyConfiguration getDependencyConfiguration(ConfigurationType configurationType, + String module, String platform) { + return new DependencyConfiguration(configurationType, + module + ":" + DEFAULT_VERSIONS.getProperty(platform + "-version")); + } +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfiguration.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfiguration.java new file mode 100644 index 00000000..078ca0cf --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfiguration.java @@ -0,0 +1,28 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +/** + * Represents a dependency to be applied to a certain sourceset configuration (e.g. implementation, compileOnly, etc.) + * In the future can expand to incorporate exclude rules, dependency substitutions, etc. + */ +public class DependencyConfiguration { + private ConfigurationType _configurationType; + private String _dependencyString; + + public DependencyConfiguration(ConfigurationType configurationType, String dependencyString) { + _configurationType = configurationType; + _dependencyString = dependencyString; + } + + public ConfigurationType getConfigurationType() { + return _configurationType; + } + + public String getDependencyString() { + return _dependencyString; + } +} \ No newline at end of file diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Language.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Language.java new file mode 100644 index 00000000..c232521f --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Language.java @@ -0,0 +1,22 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +public enum Language { + JAVA("Java"), + SCALA("Scala"); + + private String _language; + + Language(String language) { + this._language = language; + } + + @Override + public String toString() { + return _language; + } +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Platform.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Platform.java new file mode 100644 index 00000000..37ef9633 --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Platform.java @@ -0,0 +1,52 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +import com.linkedin.transport.codegen.WrapperGenerator; +import java.util.List; + + +/** + * Represents the information required to configure a given platform inside the {@link TransportPlugin} + */ +public class Platform { + + private final String _name; + private final Language _language; + private final Class _wrapperGeneratorClass; + private final List _defaultWrapperDependencyConfigurations; + private final List _defaultTestDependencyConfigurations; + + public Platform(String name, Language language, Class wrapperGeneratorClass, + List defaultWrapperDependencyConfigurations, + List defaultTestDependencyConfigurations) { + _name = name; + _language = language; + _wrapperGeneratorClass = wrapperGeneratorClass; + _defaultWrapperDependencyConfigurations = defaultWrapperDependencyConfigurations; + _defaultTestDependencyConfigurations = defaultTestDependencyConfigurations; + } + + public String getName() { + return _name; + } + + public Language getLanguage() { + return _language; + } + + public Class getWrapperGeneratorClass() { + return _wrapperGeneratorClass; + } + + public List getDefaultWrapperDependencyConfigurations() { + return _defaultWrapperDependencyConfigurations; + } + + public List getDefaultTestDependencyConfigurations() { + return _defaultTestDependencyConfigurations; + } +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/SourceSetUtils.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/SourceSetUtils.java new file mode 100644 index 00000000..5c79fc76 --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/SourceSetUtils.java @@ -0,0 +1,93 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +import java.util.Collection; +import org.codehaus.groovy.runtime.InvokerHelper; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.SourceDirectorySet; +import org.gradle.api.plugins.Convention; +import org.gradle.api.tasks.ScalaSourceSet; +import org.gradle.api.tasks.SourceSet; + + +/** + * Utility class to help manipulate a {@link SourceSet} + */ +class SourceSetUtils { + + private SourceSetUtils() { + } + + /** + * Returns the {@link SourceDirectorySet} for a given {@link SourceSet} depending on the language of the sources + */ + static SourceDirectorySet getSourceDirectorySet(SourceSet sourceSet, Language language) { + switch (language) { + case JAVA: + return sourceSet.getJava(); + case SCALA: + Convention sourceSetConvention = (Convention) InvokerHelper.getProperty(sourceSet, "convention"); + ScalaSourceSet scalaSourceSet = sourceSetConvention.getPlugin(ScalaSourceSet.class); + return scalaSourceSet.getScala(); + default: + throw new UnsupportedOperationException("Language " + language + " not supported"); + } + } + + static Configuration getConfigurationForSourceSet(Project project, SourceSet sourceSet, + ConfigurationType configurationType) { + return project.getConfigurations().getByName(getConfigurationNameForSourceSet(sourceSet, configurationType)); + } + + private static String getConfigurationNameForSourceSet(SourceSet sourceSet, ConfigurationType configurationType) { + final String configName; + switch (configurationType) { + case ANNOTATION_PROCESSOR: + configName = sourceSet.getAnnotationProcessorConfigurationName(); + break; + case IMPLEMENTATION: + configName = sourceSet.getImplementationConfigurationName(); + break; + case COMPILE_ONLY: + configName = sourceSet.getCompileOnlyConfigurationName(); + break; + case RUNTIME_ONLY: + configName = sourceSet.getRuntimeOnlyConfigurationName(); + break; + default: + throw new UnsupportedOperationException("Configuration " + configurationType + " not supported"); + } + return configName; + } + + /** + * Adds the provided dependency to the given {@link Configuration} + */ + static void addDependencyToConfiguration(Project project, Configuration configuration, Object dependency) { + configuration.withDependencies(dependencySet -> dependencySet.add(project.getDependencies().create(dependency))); + } + + /** + * Adds the provided dependency to the appropriate configurations of the given {@link SourceSet} + */ + static void addDependencyConfigurationToSourceSet(Project project, SourceSet sourceSet, + DependencyConfiguration dependencyConfiguration) { + addDependencyToConfiguration(project, + SourceSetUtils.getConfigurationForSourceSet(project, sourceSet, dependencyConfiguration.getConfigurationType()), + dependencyConfiguration.getDependencyString()); + } + + /** + * Adds the provided dependencies to the appropriate configurations of the given {@link SourceSet} + */ + static void addDependencyConfigurationsToSourceSet(Project project, SourceSet sourceSet, + Collection dependencyConfigurations) { + dependencyConfigurations.forEach( + dependencyConfiguration -> addDependencyConfigurationToSourceSet(project, sourceSet, dependencyConfiguration)); + } +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/TransportPlugin.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/TransportPlugin.java new file mode 100644 index 00000000..af707f89 --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/TransportPlugin.java @@ -0,0 +1,224 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +import com.google.common.collect.ImmutableList; +import com.linkedin.transport.plugin.tasks.GenerateWrappersTask; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.plugins.scala.ScalaPlugin; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.testing.Test; +import org.gradle.language.base.plugins.LifecycleBasePlugin; + +import static com.linkedin.transport.plugin.ConfigurationType.*; +import static com.linkedin.transport.plugin.SourceSetUtils.*; + + +/** + * A {@link Plugin} to be applied to a Transport UDF module which: + *
    + *
  1. Configures default dependencies for the main and test source sets
  2. + *
  3. Applies the Transport UDF annotation processor
  4. + *
  5. Creates a SourceSet for UDF wrapper generation
  6. + *
  7. Configures default dependencies for the platform wrappers
  8. + *
  9. Configures wrapper code generation tasks
  10. + *
  11. TODO: Configures tasks to package wrappers with appropriate shading rules
  12. + *
  13. Configures tasks to run UDF tests for the platform using the Unified Testing Framework
  14. + *
+ */ +public class TransportPlugin implements Plugin { + + public void apply(Project project) { + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { + project.getPlugins().apply(ScalaPlugin.class); + + JavaPluginConvention javaConvention = project.getConvention().getPlugin(JavaPluginConvention.class); + SourceSet mainSourceSet = javaConvention.getSourceSets().getByName("main"); + SourceSet testSourceSet = javaConvention.getSourceSets().getByName("test"); + + configureBaseSourceSets(project, mainSourceSet, testSourceSet); + Defaults.DEFAULT_PLATFORMS.forEach( + platform -> configurePlatform(project, platform, mainSourceSet, testSourceSet)); + }); + } + + /** + * Configures default dependencies for the main and test source sets + */ + private void configureBaseSourceSets(Project project, SourceSet mainSourceSet, SourceSet testSourceSet) { + addDependencyConfigurationsToSourceSet(project, mainSourceSet, Defaults.MAIN_SOURCE_SET_DEPENDENCY_CONFIGURATIONS); + addDependencyConfigurationsToSourceSet(project, testSourceSet, Defaults.TEST_SOURCE_SET_DEPENDENCY_CONFIGURATIONS); + } + + /** + * Configures SourceSets, dependencies and tasks related to each Transport UDF platform + */ + private void configurePlatform(Project project, Platform platform, SourceSet mainSourceSet, SourceSet testSourceSet) { + SourceSet sourceSet = configureSourceSet(project, platform, mainSourceSet); + TaskProvider generateWrappersTask = + configureGenerateWrappersTask(project, platform, mainSourceSet, sourceSet); + // TODO: shade and package into Jar + // Add Transport tasks to build task dependencies + project.getTasks().named(LifecycleBasePlugin.BUILD_TASK_NAME).configure(task -> { + // TODO: Replace this task with the shaded jar tasks once we create them + task.dependsOn(sourceSet.getClassesTaskName()); + }); + + TaskProvider testTask = configureTestTask(project, platform, mainSourceSet, testSourceSet); + project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(task -> task.dependsOn(testTask)); + } + + /** + * Creates and configures a {@link SourceSet} for a given platform, sets up the source directories and + * configurations and configures the default dependencies required for compilation and runtime of the wrapper + * SourceSet + */ + private SourceSet configureSourceSet(Project project, Platform platform, SourceSet mainSourceSet) { + JavaPluginConvention javaConvention = project.getConvention().getPlugin(JavaPluginConvention.class); + Path platformBaseDir = Paths.get(project.getBuildDir().toString(), "generatedWrappers", platform.getName()); + Path wrapperSourceOutputDir = platformBaseDir.resolve("sources"); + Path wrapperResourceOutputDir = platformBaseDir.resolve("resources"); + + return javaConvention.getSourceSets().create(platform.getName(), sourceSet -> { + /* + Creates a SourceSet and set the source directories for a given platform. E.g. For the Presto platform, + + presto { + java.srcDirs = ["${buildDir}/generatedWrappers/sources"] + resources.srcDirs = ["${buildDir}/generatedWrappers/resources"] + } + */ + getSourceDirectorySet(sourceSet, platform.getLanguage()).setSrcDirs(ImmutableList.of(wrapperSourceOutputDir)); + sourceSet.getResources().setSrcDirs(ImmutableList.of(wrapperResourceOutputDir)); + + /* + Sets up the configuration for the platform's wrapper SourceSet. E.g. For the Presto platform, + + configurations { + prestoImplementation.extendsFrom mainImplementation + prestoRuntimeOnly.extendsFrom mainRuntimeOnly + } + */ + getConfigurationForSourceSet(project, sourceSet, IMPLEMENTATION).extendsFrom( + getConfigurationForSourceSet(project, mainSourceSet, IMPLEMENTATION)); + getConfigurationForSourceSet(project, sourceSet, RUNTIME_ONLY).extendsFrom( + getConfigurationForSourceSet(project, mainSourceSet, RUNTIME_ONLY)); + + /* + Adds the default dependencies for the platform. E.g For the Presto platform, + + dependencies { + prestoImplementation sourceSets.main.output + prestoImplementation 'com.linkedin.transport:transportable-udfs-presto:$version' + prestoCompileOnly 'com.facebook.presto:presto-main:$version' + } + */ + addDependencyToConfiguration(project, getConfigurationForSourceSet(project, sourceSet, IMPLEMENTATION), + mainSourceSet.getOutput()); + addDependencyConfigurationsToSourceSet(project, sourceSet, platform.getDefaultWrapperDependencyConfigurations()); + }); + } + + /** + * Creates and configures a task to generate UDF wrappers for a given platform + */ + private TaskProvider configureGenerateWrappersTask(Project project, Platform platform, + SourceSet inputSourceSet, SourceSet outputSourceSet) { + + /* + Creates a generateWrapper task for a given platform. E.g For the Presto platform, + + task generatePrestoWrappers { + generatorClass = 'com.linkedin.transport.codegen.PrestoWrapperGenerator' + inputClassesDirs = sourceSets.main.output.classesDirs + sourcesOutputDir = sourceSets.presto.java.srcDirs[0] + resourcesOutputDir = sourceSets.presto.resources.srcDirs[0] + dependsOn classes + } + + prestoClasses.dependsOn(generatePrestoWrappers) + */ + String taskName = outputSourceSet.getTaskName("generate", "Wrappers"); + File sourcesOutputDir = + getSourceDirectorySet(outputSourceSet, platform.getLanguage()).getSrcDirs().iterator().next(); + File resourcesOutputDir = outputSourceSet.getResources().getSrcDirs().iterator().next(); + + TaskProvider generateWrappersTask = + project.getTasks().register(taskName, GenerateWrappersTask.class, task -> { + task.setDescription("Generates Transport UDF wrappers for " + platform.getName()); + task.getGeneratorClass().set(platform.getWrapperGeneratorClass().getName()); + task.getInputClassesDirs().set(inputSourceSet.getOutput().getClassesDirs()); + task.getSourcesOutputDir().set(sourcesOutputDir); + task.getResourcesOutputDir().set(resourcesOutputDir); + task.dependsOn(project.getTasks().named(inputSourceSet.getClassesTaskName())); + }); + + project.getTasks() + .named(outputSourceSet.getCompileTaskName(platform.getLanguage().toString())) + .configure(task -> task.dependsOn(generateWrappersTask)); + + return generateWrappersTask; + } + + /** + * Creates and configures a task to run tests written using the Unified Testing Framework against a given platform + */ + private TaskProvider configureTestTask(Project project, Platform platform, SourceSet mainSourceSet, + SourceSet testSourceSet) { + + /* + Configures the classpath configuration to run platform-specific tests. E.g. For the Presto platform, + + configurations { + prestoTestClasspath { + extendsFrom testImplementation + } + } + + dependencies { + prestoTestClasspath sourceSets.main.output, sourceSets.test.output + prestoTestClasspath 'com.linkedin.transport:transportable-udfs-test-presto' + } + */ + Configuration testClasspath = project.getConfigurations() + .create(platform.getName() + "TestClasspath", + config -> config.extendsFrom(getConfigurationForSourceSet(project, testSourceSet, IMPLEMENTATION))); + addDependencyToConfiguration(project, testClasspath, mainSourceSet.getOutput()); + addDependencyToConfiguration(project, testClasspath, testSourceSet.getOutput()); + platform.getDefaultTestDependencyConfigurations() + .forEach(dependencyConfiguration -> addDependencyToConfiguration(project, testClasspath, + dependencyConfiguration.getDependencyString())); + + /* + Creates the test task for a given platform. E.g. For the Presto platform, + + task prestoTest(type: Test, dependsOn: test) { + group 'Verification' + description 'Runs the Presto tests.' + testClassesDirs = sourceSets.test.output.classesDirs + classpath = configurations.prestoTestClasspath + useTestNG() + } + */ + + return project.getTasks().register(platform.getName() + "Test", Test.class, task -> { + task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); + task.setDescription("Runs Transport UDF tests on " + platform.getName()); + task.setTestClassesDirs(testSourceSet.getOutput().getClassesDirs()); + task.setClasspath(testClasspath); + task.useTestNG(); + task.mustRunAfter(project.getTasks().named("test")); + }); + } +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/tasks/GenerateWrappersTask.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/tasks/GenerateWrappersTask.java new file mode 100644 index 00000000..3e93e9b1 --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/tasks/GenerateWrappersTask.java @@ -0,0 +1,95 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin.tasks; + +import com.linkedin.transport.codegen.WrapperGenerator; +import com.linkedin.transport.codegen.WrapperGeneratorContext; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; +import java.util.stream.StreamSupport; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; + + +/** + * A Gradle task which generates Transport UDF wrappers given a UDF metadata file and wrapper generator class + */ +public class GenerateWrappersTask extends DefaultTask { + + private final Property _generatorClass; + + private final Property _inputClassesDirs; + + private final DirectoryProperty _sourcesOutputDir; + + private final DirectoryProperty _resourcesOutputDir; + + public GenerateWrappersTask() { + _generatorClass = getProject().getObjects().property(String.class); + _inputClassesDirs = getProject().getObjects().property(FileCollection.class); + _sourcesOutputDir = getProject().getObjects().directoryProperty(); + _resourcesOutputDir = getProject().getObjects().directoryProperty(); + } + + @Input + public Property getGeneratorClass() { + return _generatorClass; + } + + @InputFiles + public Property getInputClassesDirs() { + return _inputClassesDirs; + } + + @OutputDirectory + public DirectoryProperty getSourcesOutputDir() { + return _sourcesOutputDir; + } + + @OutputDirectory + public DirectoryProperty getResourcesOutputDir() { + return _resourcesOutputDir; + } + + @TaskAction + public void generateWrappers() { + // TODO: Use Gradle worker API to generate wrappers concurrently + WrapperGenerator generator; + try { + generator = (WrapperGenerator) Class.forName(_generatorClass.get()).getConstructor().newInstance(); + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Could not create object of class: " + _generatorClass.get(), e); + } + generator.generateWrappers( + new WrapperGeneratorContext(getUDFMetadataFile(_inputClassesDirs.get()), _sourcesOutputDir.getAsFile().get(), + _resourcesOutputDir.getAsFile().get())); + } + + /** + * Finds the location for the UDF metadata file generated by the annotation processor in the classes directories of + * the input sources + */ + private File getUDFMetadataFile(FileCollection inputClassesDirs) { + Optional udfMetadataFile = StreamSupport.stream(inputClassesDirs.spliterator(), true) + .map(folder -> folder.toPath().resolve("META-INF/transport-udfs/metadata.json").toFile()) + .filter(File::exists) + .findFirst(); + + if (udfMetadataFile.isPresent()) { + return udfMetadataFile.get(); + } else { + throw new RuntimeException( + "Could not find UDF metadata file in the input directories: " + inputClassesDirs.getFiles().toString()); + } + } +}