diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bda2ac..13c0fa9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: run: sbt '++ ${{ matrix.scala }}' compile test scripted - name: Compress target directories - run: tar cf targets.tar target project/target + run: tar cf targets.tar target api/target bridge/target plugin/target project/target - name: Upload target directories uses: actions/upload-artifact@v4 diff --git a/README.md b/README.md index 96d4bb0..56b1ddf 100644 --- a/README.md +++ b/README.md @@ -18,46 +18,58 @@ For instance, add the following lines to `project/plugins.sbt`: ``` addSbtPlugin("com.github.sbt" % "sbt-avro" % "3.5.0") - -// Java sources compiled with one version of Avro might be incompatible with a -// different version of the Avro library. Therefore we specify the compiler -// version here explicitly. -libraryDependencies += "org.apache.avro" % "avro-compiler" % "1.12.0" ``` -Add the library dependency to `build.sbt`: +Enable the plugin in your `build.sbt` and select the desired avro version to use: ``` -libraryDependencies += "org.apache.avro" % "avro" % avroCompilerVersion +enablePlugins(SbtAvro) +avroVersion := "1.12.0" ``` +## Config + +An `avro` configuration will be added to the project. Libraries defined with this scope will be loaded by the sbt plugin +to generate the avro classes. + ## Settings -| Name | Default | Description | -|:-------------------------------------------|:----------------------------------------------|:----------------------------------------------------------------------------------------| -| `avroSource` | `sourceDirectory` / `avro` | Source directory with `*.avsc`, `*.avdl` and `*.avpr` files. | -| `avroSpecificRecords` | `Seq.empty` | List of avro generated classes to recompile with current avro version and settings. | -| `avroSchemaParserBuilder` | `DefaultSchemaParserBuilder.default()` | `.avsc` schema parser builder | -| `avroUnpackDependencies` / `includeFilter` | All avro specifications | Avro specification files from dependencies to unpack | -| `avroUnpackDependencies` / `excludeFilter` | Hidden files | Avro specification files from dependencies to exclude from unpacking | -| `avroUnpackDependencies` / `target` | `sourceManaged` / `avro` / `$config` | Target directory for schemas packaged in the dependencies | -| `avroGenerate` / `target` | `sourceManaged` / `compiled_avro` / `$config` | Source directory for generated `.java` files. | -| `avroDependencyIncludeFilter` | `source` typed `avro` classifier artifacts | Dependencies containing avro schema to be unpacked for generation | -| `avroIncludes` | `Seq()` | Paths with extra `*.avsc` files to be included in compilation. | -| `packageAvro` / `artifactClassifier` | `Some("avro")` | Classifier for avro artifact | -| `packageAvro` / `publishArtifact` | `false` | Enable / Disable avro artifact publishing | -| `avroStringType` | `CharSequence` | Type for representing strings. Possible values: `CharSequence`, `String`, `Utf8`. | -| `avroUseNamespace` | `false` | Validate that directory layout reflects namespaces, i.e. `com/myorg/MyRecord.avsc`. | -| `avroFieldVisibility` | `public` | Field Visibility for the properties. Possible values: `private`, `public`. | -| `avroEnableDecimalLogicalType` | `true` | Use `java.math.BigDecimal` instead of `java.nio.ByteBuffer` for logical type `decimal`. | -| `avroOptionalGetters` | `false` (requires avro `1.10+`) | Generate getters that return `Optional` for nullable fields. | - -## Tasks +### Project settings + +| Name | Default | Description | +|:-------------------------------|:-------------------------------------------------------------------------|:----------------------------------------------------------------------------------------| +| `avroAdditionalDependencies` | `avro-compiler % avroVersion % "avro"`, `avro % avroVersion % "compile"` | Additional dependencies to be added to library dependencies. | +| `avroCompiler` | `com.github.sbt.avro.AvroCompilerBridge` | Sbt avro compiler class. | +| `avroCreateSetters` | `true` | Generate setters. | +| `avroDependencyIncludeFilter` | `source` typed `avro` classifier artifacts | Filter for including modules containing avro dependencies. | +| `avroEnableDecimalLogicalType` | `true` | Use `java.math.BigDecimal` instead of `java.nio.ByteBuffer` for logical type `decimal`. | +| `avroFieldVisibility` | `public` | Field visibility for the properties. Possible values: `private`, `public`. | +| `avroOptionalGetters` | `false` (requires avro `1.10+`) | Generate getters that return `Optional` for nullable fields. | +| `avroStringType` | `CharSequence` | Type for representing strings. Possible values: `CharSequence`, `String`, `Utf8`. | +| `avroUseNamespace` | `false` | Validate that directory layout reflects namespaces, i.e. `com/myorg/MyRecord.avsc`. | +| `avroVersion` | `1.12.0` | Avro version to use in the project. | + +### Scoped settings (Compile/Test) + +| Name | Default | Description | +|:-------------------------------------------|:----------------------------------------------|:-----------------------------------------------------------------------------------------------------| +| `avroGenerate` / `target` | `sourceManaged` / `compiled_avro` / `$config` | Source directory for generated `.java` files. | +| `avroSource` | `sourceDirectory` / `$config` / `avro` | Default Avro source directory for `*.avsc`, `*.avdl` and `*.avpr` files. | +| `avroSpecificRecords` | `Seq.empty` | List of fully qualified Avro record class names to recompile with current avro version and settings. | +| `avroUmanagedSourceDirectories` | `Seq(avroSource)` | Unmanaged Avro source directories, which contain manually created sources. | +| `avroUnpackDependencies` / `excludeFilter` | `HiddenFileFilter` | Filter for excluding avro specification files from unpacking. | +| `avroUnpackDependencies` / `includeFilter` | `AllPassFilter` | Filter for including avro specification files to unpack. | +| `avroUnpackDependencies` / `target` | `sourceManaged` / `avro` / `$config` | Target directory for schemas packaged in the dependencies | +| `packageAvro` / `artifactClassifier` | `Some("avro")` | Classifier for avro artifact | +| `packageAvro` / `publishArtifact` | `false` | Enable / Disable avro artifact publishing | + + +## Scoped Tasks (Compile/Test) | Name | Description | |:-------------------------|:--------------------------------------------------------------------------------------------------| -| `avroUnpackDependencies` | Unpack avro schemas from dependencies. This task is automatically executed before `avroGenerate`. | | `avroGenerate` | Generate Java sources for Avro schemas. This task is automatically executed before `compile`. | +| `avroUnpackDependencies` | Unpack avro schemas from dependencies. This task is automatically executed before `avroGenerate`. | | `packageAvro` | Produces an avro artifact, such as a jar containing avro schemas. | ## Examples @@ -72,7 +84,7 @@ If you depend on an artifact with previously generated avro java classes with st you can recompile them with `String` by also adding the following ```sbt -Compile / avroSpecificRecords += classOf[com.example.MyAvroRecord] // lib must be declared in project/plugins.sbt +Compile / avroSpecificRecords += "com.example.MyAvroRecord" // lib must be added in the avro scope ``` ## Packaging Avro files diff --git a/api/src/main/java/com/github/sbt/avro/AvroCompiler.java b/api/src/main/java/com/github/sbt/avro/AvroCompiler.java new file mode 100644 index 0000000..5ffac68 --- /dev/null +++ b/api/src/main/java/com/github/sbt/avro/AvroCompiler.java @@ -0,0 +1,18 @@ +package com.github.sbt.avro; + +import java.io.File; + +public interface AvroCompiler { + + void setStringType(String stringType); + void setFieldVisibility(String fieldVisibility); + void setUseNamespace(boolean useNamespace); + void setEnableDecimalLogicalType(boolean enableDecimalLogicalType); + void setCreateSetters(boolean createSetters); + void setOptionalGetters(boolean optionalGetters); + + void recompile(Class[] records, File target) throws Exception; + void compileIdls(File[] idls, File target) throws Exception; + void compileAvscs(AvroFileRef[] avscs, File target) throws Exception; + void compileAvprs(File[] avprs, File target) throws Exception; +} diff --git a/src/main/java/com/github/sbt/avro/mojo/AvroFileRef.java b/api/src/main/java/com/github/sbt/avro/AvroFileRef.java similarity index 97% rename from src/main/java/com/github/sbt/avro/mojo/AvroFileRef.java rename to api/src/main/java/com/github/sbt/avro/AvroFileRef.java index 30cfc84..814bea3 100644 --- a/src/main/java/com/github/sbt/avro/mojo/AvroFileRef.java +++ b/api/src/main/java/com/github/sbt/avro/AvroFileRef.java @@ -1,4 +1,4 @@ -package com.github.sbt.avro.mojo; +package com.github.sbt.avro; import java.io.File; import java.util.Objects; diff --git a/bridge/src/main/java/com/github/sbt/avro/AvroCompilerBridge.java b/bridge/src/main/java/com/github/sbt/avro/AvroCompilerBridge.java new file mode 100644 index 0000000..fa9717b --- /dev/null +++ b/bridge/src/main/java/com/github/sbt/avro/AvroCompilerBridge.java @@ -0,0 +1,152 @@ +package com.github.sbt.avro; + +import org.apache.avro.Schema; +import org.apache.avro.specific.SpecificRecord; + +import org.apache.avro.Protocol; +import org.apache.avro.compiler.idl.Idl; +import org.apache.avro.compiler.specific.SpecificCompiler; +import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility; +import org.apache.avro.generic.GenericData.StringType; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +public class AvroCompilerBridge implements AvroCompiler { + + private static final AvroVersion AVRO_1_9_0 = new AvroVersion(1, 9, 0); + private static final AvroVersion AVRO_1_10_0 = new AvroVersion(1, 10, 0); + + private final AvroVersion avroVersion = AvroVersion.getRuntimeVersion(); + + private StringType stringType; + private FieldVisibility fieldVisibility; + private boolean useNamespace; + private boolean enableDecimalLogicalType; + private boolean createSetters; + private boolean optionalGetters; + + protected Schema.Parser createParser() { + return new Schema.Parser(); + } + + @Override + public void setStringType(String stringType) { + this.stringType = StringType.valueOf(stringType); + } + + @Override + public void setFieldVisibility(String fieldVisibility) { + this.fieldVisibility = FieldVisibility.valueOf(fieldVisibility); + } + + @Override + public void setUseNamespace(boolean useNamespace) { + this.useNamespace = useNamespace; + } + + @Override + public void setEnableDecimalLogicalType(boolean enableDecimalLogicalType) { + this.enableDecimalLogicalType = enableDecimalLogicalType; + } + + @Override + public void setCreateSetters(boolean createSetters) { + this.createSetters = createSetters; + } + + @Override + public void setOptionalGetters(boolean optionalGetters) { + this.optionalGetters = optionalGetters; + } + + @Override + public void recompile(Class[] records, File target) throws Exception { + AvscFilesCompiler compiler = new AvscFilesCompiler(this::createParser); + compiler.setStringType(stringType); + compiler.setFieldVisibility(fieldVisibility); + compiler.setUseNamespace(useNamespace); + compiler.setEnableDecimalLogicalType(enableDecimalLogicalType); + compiler.setCreateSetters(createSetters); + if (avroVersion.compareTo(AVRO_1_9_0) >= 0) { + compiler.setGettersReturnOptional(optionalGetters); + } + if (avroVersion.compareTo(AVRO_1_10_0) >= 0) { + compiler.setOptionalGettersForNullableFieldsOnly(optionalGetters); + } + compiler.setTemplateDirectory("/org/apache/avro/compiler/specific/templates/java/classic/"); + + Set> classes = new HashSet<>(); + for (Class record : records) { + System.out.println("Recompiling Avro record: " + record.getName()); + classes.add((Class) record); + } + compiler.compileClasses(classes, target); + } + + @Override + public void compileIdls(File[] idls, File target) throws Exception { + for (File idl : idls) { + System.out.println("Compiling Avro IDL: " + idl); + Idl parser = new Idl(idl); + Protocol protocol = parser.CompilationUnit(); + SpecificCompiler compiler = new SpecificCompiler(protocol); + compiler.setStringType(stringType); + compiler.setFieldVisibility(fieldVisibility); + compiler.setEnableDecimalLogicalType(enableDecimalLogicalType); + compiler.setCreateSetters(createSetters); + if (avroVersion.compareTo(AVRO_1_9_0) >= 0) { + compiler.setGettersReturnOptional(optionalGetters); + } + if (avroVersion.compareTo(AVRO_1_10_0) >= 0) { + compiler.setOptionalGettersForNullableFieldsOnly(optionalGetters); + } + compiler.compileToDestination(null, target); + } + } + + @Override + public void compileAvscs(AvroFileRef[] avscs, File target) throws Exception { + AvscFilesCompiler compiler = new AvscFilesCompiler(this::createParser); + compiler.setStringType(stringType); + compiler.setFieldVisibility(fieldVisibility); + compiler.setUseNamespace(useNamespace); + compiler.setEnableDecimalLogicalType(enableDecimalLogicalType); + compiler.setCreateSetters(createSetters); + if (avroVersion.compareTo(AVRO_1_9_0) >= 0) { + compiler.setGettersReturnOptional(optionalGetters); + } + if (avroVersion.compareTo(AVRO_1_10_0) >= 0) { + compiler.setOptionalGettersForNullableFieldsOnly(optionalGetters); + } + compiler.setTemplateDirectory("/org/apache/avro/compiler/specific/templates/java/classic/"); + + Set files = new HashSet<>(); + for (AvroFileRef ref : avscs) { + System.out.println("Compiling Avro schema: " + ref.getFile()); + files.add(ref); + } + compiler.compileFiles(Set.of(avscs), target); + } + + @Override + public void compileAvprs(File[] avprs, File target) throws Exception { + for (File avpr : avprs) { + System.out.println("Compiling Avro protocol: " + avpr); + Protocol protocol = Protocol.parse(avpr); + SpecificCompiler compiler = new SpecificCompiler(protocol); + compiler.setStringType(stringType); + compiler.setFieldVisibility(fieldVisibility); + compiler.setEnableDecimalLogicalType(enableDecimalLogicalType); + compiler.setCreateSetters(createSetters); + if (avroVersion.compareTo(AVRO_1_9_0) >= 0) { + compiler.setGettersReturnOptional(optionalGetters); + } + if (avroVersion.compareTo(AVRO_1_10_0) >= 0) { + compiler.setOptionalGettersForNullableFieldsOnly(optionalGetters); + } + compiler.compileToDestination(null, target); + } + } +} diff --git a/bridge/src/main/java/com/github/sbt/avro/AvroVersion.java b/bridge/src/main/java/com/github/sbt/avro/AvroVersion.java new file mode 100644 index 0000000..2f990e0 --- /dev/null +++ b/bridge/src/main/java/com/github/sbt/avro/AvroVersion.java @@ -0,0 +1,48 @@ +package com.github.sbt.avro; + +import org.apache.avro.Schema; + +public class AvroVersion implements Comparable { + + private final int major; + private final int minor; + private final int patch; + + public AvroVersion(int major, int minor, int patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + int getMajor() { + return major; + } + + int getMinor() { + return minor; + } + + int getPatch() { + return patch; + } + + static AvroVersion getRuntimeVersion() { + String[] parts = Schema.class.getPackage().getImplementationVersion().split("\\.", 3); + int major = Integer.parseInt(parts[0]); + int minor = Integer.parseInt(parts[1]); + int patch = Integer.parseInt(parts[2]); + return new AvroVersion(major, minor, patch); + } + + + @Override + public int compareTo(AvroVersion o) { + if (major != o.major) { + return major - o.major; + } else if (minor != o.minor) { + return minor - o.minor; + } else { + return patch - o.patch; + } + } +} diff --git a/src/main/java/com/github/sbt/avro/mojo/AvscFilesCompiler.java b/bridge/src/main/java/com/github/sbt/avro/AvscFilesCompiler.java similarity index 84% rename from src/main/java/com/github/sbt/avro/mojo/AvscFilesCompiler.java rename to bridge/src/main/java/com/github/sbt/avro/AvscFilesCompiler.java index 770dba6..7abcbc5 100644 --- a/src/main/java/com/github/sbt/avro/mojo/AvscFilesCompiler.java +++ b/bridge/src/main/java/com/github/sbt/avro/AvscFilesCompiler.java @@ -1,40 +1,36 @@ -package com.github.sbt.avro.mojo; +package com.github.sbt.avro; import org.apache.avro.AvroRuntimeException; -import org.apache.avro.AvroTypeException; import org.apache.avro.Schema; -import org.apache.avro.SchemaParseException; import org.apache.avro.compiler.specific.SpecificCompiler; import org.apache.avro.generic.GenericData; import org.apache.avro.specific.SpecificData; import org.apache.avro.specific.SpecificRecord; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.*; +import java.util.function.Supplier; import java.util.stream.Collectors; public class AvscFilesCompiler { - private static final Logger LOG = LoggerFactory.getLogger(AvscFilesCompiler.class); - - private final SchemaParserBuilder builder; + private final Supplier parserSupplier; private Schema.Parser schemaParser; + private String templateDirectory; private GenericData.StringType stringType; private SpecificCompiler.FieldVisibility fieldVisibility; private boolean useNamespace; private boolean enableDecimalLogicalType; private boolean createSetters; - private Optional optionalGetters = Optional.empty(); + private Boolean gettersReturnOptional; + private Boolean optionalGettersForNullableFieldsOnly; private Map compileExceptions; - private boolean logCompileExceptions; - public AvscFilesCompiler(SchemaParserBuilder builder) { - this.builder = builder; - this.schemaParser = builder.build(); + public AvscFilesCompiler(Supplier parserSupplier) { + this.parserSupplier = parserSupplier; + this.schemaParser = parserSupplier.get(); } public void compileFiles(Set files, File outputDirectory) { @@ -67,9 +63,6 @@ public void compileFiles(Set files, File outputDirectory) { for (AvroFileRef file : uncompiledFiles) { Exception e = compileExceptions.get(file); if (e != null) { - if (logCompileExceptions) { - LOG.error(file.toString(), e); - } ex.addSuppressed(e); } } @@ -108,9 +101,6 @@ public void compileClasses(Set> classes, File ou for (Class clazz : uncompiledClasses) { Exception e = compileExceptions.get(clazz); if (e != null) { - if (logCompileExceptions) { - LOG.error(clazz.toString(), e); - } ex.addSuppressed(e); } } @@ -142,9 +132,12 @@ private boolean tryCompile(File src, Schema schema, File outputDirectory) { compiler.setFieldVisibility(fieldVisibility); compiler.setEnableDecimalLogicalType(enableDecimalLogicalType); compiler.setCreateSetters(createSetters); - if (optionalGetters.isPresent()) { - compiler.setGettersReturnOptional(optionalGetters.get()); - compiler.setOptionalGettersForNullableFieldsOnly(optionalGetters.get()); + + if (gettersReturnOptional != null) { + compiler.setGettersReturnOptional(gettersReturnOptional); + } + if (optionalGettersForNullableFieldsOnly != null) { + compiler.setOptionalGettersForNullableFieldsOnly(optionalGettersForNullableFieldsOnly); } try { @@ -160,7 +153,7 @@ private boolean tryCompile(File src, Schema schema, File outputDirectory) { private Schema.Parser stashParser() { // on failure Schema.Parser changes cache state. // We want last successful state. - Schema.Parser parser = builder.build(); + Schema.Parser parser = parserSupplier.get(); Set predefinedTypes = parser.getTypes().keySet(); Map compiledTypes = schemaParser.getTypes(); compiledTypes.keySet().removeAll(predefinedTypes); @@ -212,11 +205,11 @@ public void setCreateSetters(boolean createSetters) { this.createSetters = createSetters; } - public void setLogCompileExceptions(final boolean logCompileExceptions) { - this.logCompileExceptions = logCompileExceptions; + public void setGettersReturnOptional(final boolean gettersReturnOptional) { + this.gettersReturnOptional = gettersReturnOptional; } - public void setOptionalGetters(final boolean optionalGetters) { - this.optionalGetters = Optional.of(optionalGetters); + public void setOptionalGettersForNullableFieldsOnly(final boolean optionalGettersForNullableFieldsOnly) { + this.optionalGettersForNullableFieldsOnly = optionalGettersForNullableFieldsOnly; } } diff --git a/src/main/java/com/github/sbt/avro/mojo/SchemaGenerationException.java b/bridge/src/main/java/com/github/sbt/avro/SchemaGenerationException.java similarity index 87% rename from src/main/java/com/github/sbt/avro/mojo/SchemaGenerationException.java rename to bridge/src/main/java/com/github/sbt/avro/SchemaGenerationException.java index 923cdf1..53105dd 100644 --- a/src/main/java/com/github/sbt/avro/mojo/SchemaGenerationException.java +++ b/bridge/src/main/java/com/github/sbt/avro/SchemaGenerationException.java @@ -1,4 +1,4 @@ -package com.github.sbt.avro.mojo; +package com.github.sbt.avro; public class SchemaGenerationException extends RuntimeException { diff --git a/src/test/java/com/github/sbt/avro/test/TestSpecificRecord.java b/bridge/src/test/java/com/github/sbt/avro/test/TestSpecificRecord.java similarity index 100% rename from src/test/java/com/github/sbt/avro/test/TestSpecificRecord.java rename to bridge/src/test/java/com/github/sbt/avro/test/TestSpecificRecord.java diff --git a/src/test/java/com/github/sbt/avro/test/TestSpecificRecordParent.java b/bridge/src/test/java/com/github/sbt/avro/test/TestSpecificRecordParent.java similarity index 100% rename from src/test/java/com/github/sbt/avro/test/TestSpecificRecordParent.java rename to bridge/src/test/java/com/github/sbt/avro/test/TestSpecificRecordParent.java diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/_a.avsc b/bridge/src/test/resources/avro/_a.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/_a.avsc rename to bridge/src/test/resources/avro/_a.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/_b.avsc b/bridge/src/test/resources/avro/_b.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/_b.avsc rename to bridge/src/test/resources/avro/_b.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/_c.avsc b/bridge/src/test/resources/avro/_c.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/_c.avsc rename to bridge/src/test/resources/avro/_c.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/_d.avsc b/bridge/src/test/resources/avro/_d.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/_d.avsc rename to bridge/src/test/resources/avro/_d.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/_e.avsc b/bridge/src/test/resources/avro/_e.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/_e.avsc rename to bridge/src/test/resources/avro/_e.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/a.avsc b/bridge/src/test/resources/avro/a.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/a.avsc rename to bridge/src/test/resources/avro/a.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/b.avsc b/bridge/src/test/resources/avro/b.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/b.avsc rename to bridge/src/test/resources/avro/b.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/c.avsc b/bridge/src/test/resources/avro/c.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/c.avsc rename to bridge/src/test/resources/avro/c.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/d.avsc b/bridge/src/test/resources/avro/d.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/d.avsc rename to bridge/src/test/resources/avro/d.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/e.avsc b/bridge/src/test/resources/avro/e.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/e.avsc rename to bridge/src/test/resources/avro/e.avsc diff --git a/src/test/resources/avro/test_records.avsc b/bridge/src/test/resources/avro/test_records.avsc similarity index 100% rename from src/test/resources/avro/test_records.avsc rename to bridge/src/test/resources/avro/test_records.avsc diff --git a/src/test/scala/com/github/sbt/avro/SbtAvroSpec.scala b/bridge/src/test/scala/com/github/sbt/avro/AvscFilesCompilerSpec.scala similarity index 75% rename from src/test/scala/com/github/sbt/avro/SbtAvroSpec.scala rename to bridge/src/test/scala/com/github/sbt/avro/AvscFilesCompilerSpec.scala index f24db24..038930d 100644 --- a/src/test/scala/com/github/sbt/avro/SbtAvroSpec.scala +++ b/bridge/src/test/scala/com/github/sbt/avro/AvscFilesCompilerSpec.scala @@ -1,22 +1,31 @@ package com.github.sbt.avro -import com.github.sbt.avro.mojo.AvroFileRef import com.github.sbt.avro.test.{TestSpecificRecord, TestSpecificRecordParent} +import org.apache.avro.Schema import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility import org.apache.avro.generic.GenericData.StringType +import org.apache.avro.specific.SpecificRecord import org.specs2.mutable.Specification -import sbt.util.Logger import java.io.File import java.nio.file.Files +import scala.collection.JavaConverters._ -class SbtAvroSpec extends Specification { - val builder = DefaultSchemaParserBuilder.default() +class AvscFilesCompilerSpec extends Specification { val sourceDir = new File(getClass.getClassLoader.getResource("avro").toURI) - val targetDir = Files.createTempDirectory("sbt-avro").toFile + val targetDir = Files.createTempDirectory("sbt-avro-compiler-bridge").toFile val packageDir = new File(targetDir, "com/github/sbt/avro/test") - val logger = Logger.Null + + val compiler = new AvscFilesCompiler(() => new Schema.Parser()) + compiler.setUseNamespace(false) + compiler.setStringType(StringType.CharSequence) + compiler.setFieldVisibility(FieldVisibility.PRIVATE) + compiler.setEnableDecimalLogicalType(true) + compiler.setGettersReturnOptional(true) + compiler.setOptionalGettersForNullableFieldsOnly(true) + compiler.setCreateSetters(true) + compiler.setTemplateDirectory("/org/apache/avro/compiler/specific/templates/java/classic/") "It should be possible to compile types depending on others if source files are provided in right order" >> { val fullyQualifiedNames = Seq( @@ -62,18 +71,7 @@ class SbtAvroSpec extends Specification { _eJavaFile.delete() val refs = sourceFiles.map(s => new AvroFileRef(sourceDir, s.getName)) - SbtAvro.compileAvscs( - refs = refs, - target = targetDir, - log = logger, - stringType = StringType.CharSequence, - fieldVisibility = FieldVisibility.PRIVATE, - enableDecimalLogicalType = true, - useNamespace = false, - optionalGetters = None, - createSetters = true, - builder = builder - ) + compiler.compileFiles(refs.toSet.asJava, targetDir) aJavaFile.isFile must beTrue bJavaFile.isFile must beTrue @@ -90,21 +88,13 @@ class SbtAvroSpec extends Specification { "It should be possible to compile types depending on others if classes are provided in right order" >> { // TestSpecificRecordParent and TestSpecificRecord were previously generated from test_records.avsc - SbtAvro.recompile( - records = Seq( + compiler.compileClasses( + Set[Class[_ <: SpecificRecord]]( // put parent 1st classOf[TestSpecificRecordParent], classOf[TestSpecificRecord] - ), - target = targetDir, - log = logger, - stringType = StringType.CharSequence, - fieldVisibility = FieldVisibility.PRIVATE, - enableDecimalLogicalType = true, - useNamespace = false, - optionalGetters = None, - createSetters = true, - builder = builder + ).asJava, + targetDir ) val record = new File(packageDir, "TestSpecificRecord.java") diff --git a/build.sbt b/build.sbt index e3ffc2d..e05cc68 100644 --- a/build.sbt +++ b/build.sbt @@ -2,11 +2,35 @@ ThisBuild / dynverSonatypeSnapshots := true ThisBuild / version := { val orig = (ThisBuild / version).value - if (orig.endsWith("-SNAPSHOT")) "3.5.1-SNAPSHOT" else orig + if (orig.endsWith("-SNAPSHOT")) "4.0.0-SNAPSHOT" else orig } -ThisBuild / scalaVersion := "2.12.20" + +// metadata +ThisBuild / organization := "com.github.sbt" +ThisBuild / organizationName := "sbt" +ThisBuild / organizationHomepage := Some(url("https://www.scala-sbt.org/")) +ThisBuild / homepage := Some(url("https://github.com/sbt/sbt-avro")) +ThisBuild / licenses += ("BSD 3-Clause", url("https://github.com/sbt/sbt-avro/blob/main/LICENSE")) +ThisBuild / scmInfo := Some( + ScmInfo(url("https://github.com/sbt/sbt-avro"), "scm:git:git@github.com:sbt/sbt-avro.git") +) +ThisBuild / developers := List( + Developer( + id = "nevillelyh", + name = "Neville Li", + email = "@nevillelyh", + url = url("https://www.lyh.me/") + ), + Developer( + id = "RustedBones", + name = "Michel Davit", + email = "michel@davit.fr", + url = url("https://michel.davit.fr") + ) +) // sbt-github-actions +ThisBuild / scalaVersion := "2.12.20" ThisBuild / githubWorkflowBuild := Seq( WorkflowStep.Sbt(name = Some("Build project"), commands = List("compile", "test", "scripted")) ) @@ -33,39 +57,49 @@ ThisBuild / githubWorkflowPublish := Seq( ) ) -lazy val `sbt-avro`: Project = project +lazy val `sbt-avro-parent`: Project = project .in(file(".")) - .enablePlugins(SbtPlugin) .settings( - organization := "com.github.sbt", - organizationName := "sbt", - organizationHomepage := Some(url("https://www.scala-sbt.org/")), - homepage := Some(url("https://github.com/sbt/sbt-avro")), - licenses += ("BSD 3-Clause", url("https://github.com/sbt/sbt-avro/blob/main/LICENSE")), - description := "Sbt plugin for compiling Avro sources", - scmInfo := Some( - ScmInfo(url("https://github.com/sbt/sbt-avro"), "scm:git:git@github.com:sbt/sbt-avro.git") - ), - developers := List( - Developer( - id = "nevillelyh", - name = "Neville Li", - email = "@nevillelyh", - url = url("https://www.lyh.me/") - ), - Developer( - id = "RustedBones", - name = "Michel Davit", - email = "michel@davit.fr", - url = url("https://michel.davit.fr") - ) - ), - pluginCrossBuild / sbtVersion := "1.3.0", - Compile / scalacOptions ++= Seq("-deprecation"), + publish / skip := true + ) + .aggregate( + `sbt-avro-compiler-api`, + `sbt-avro-compiler-bridge`, + `sbt-avro` + ) + +lazy val `sbt-avro-compiler-api`: Project = project + .in(file("api")) + .settings( + crossPaths := false, + autoScalaLibrary := false + ) + +lazy val `sbt-avro-compiler-bridge`: Project = project + .in(file("bridge")) + .dependsOn(`sbt-avro-compiler-api`) + .settings( + crossPaths := false, + autoScalaLibrary := false, libraryDependencies ++= Seq( Dependencies.Provided.AvroCompiler, Dependencies.Test.Specs2Core - ), + ) + ) + +lazy val `sbt-avro`: Project = project + .in(file("plugin")) + .dependsOn( + `sbt-avro-compiler-api`, + `sbt-avro-compiler-bridge` % "test" + ) + .enablePlugins(BuildInfoPlugin, SbtPlugin) + .settings( + description := "Sbt plugin for compiling Avro sources", + buildInfoKeys := Seq[BuildInfoKey](name, version), + buildInfoPackage := "com.github.sbt.avro", + pluginCrossBuild / sbtVersion := "1.3.0", + Compile / scalacOptions ++= Seq("-deprecation"), scriptedLaunchOpts ++= Seq( "-Xmx1024M", "-Dplugin.version=" + version.value diff --git a/plugin/src/main/scala/com/github/sbt/avro/SbtAvro.scala b/plugin/src/main/scala/com/github/sbt/avro/SbtAvro.scala new file mode 100644 index 0000000..f7f5e12 --- /dev/null +++ b/plugin/src/main/scala/com/github/sbt/avro/SbtAvro.scala @@ -0,0 +1,269 @@ +package com.github.sbt.avro + +import sbt.Keys.* +import sbt.* +import Path.relativeTo +import sbt.librarymanagement.DependencyFilter + +import java.io.File +import java.net.URLClassLoader + +/** Plugin for generating the Java sources for Avro schemas and protocols. */ +object SbtAvro extends AutoPlugin { + + // Force Log4J to not use JMX to avoid duplicate mbeans registration due to multiple classloader + sys.props("log4j2.disableJmx") = "true" + + val Avro: Configuration = config("avro") + val AvroClassifier = "avro" + + private[avro] val AvroAvrpFilter: NameFilter = "*.avpr" + private[avro] val AvroAvdlFilter: NameFilter = "*.avdl" + private[avro] val AvroAvscFilter: NameFilter = "*.avsc" + private[avro] val AvroFilter: NameFilter = AvroAvscFilter | AvroAvdlFilter | AvroAvrpFilter + + private[avro] val JavaFileFilter: NameFilter = "*.java" + + object autoImport { + + import Defaults._ + + // format: off + val avroAdditionalDependencies = settingKey[Seq[ModuleID]]("Additional dependencies to be added to library dependencies.") + val avroCompiler = settingKey[String]("Sbt avro compiler class.") + val avroCreateSetters = settingKey[Boolean]("Generate setters.") + val avroDependencyIncludeFilter = settingKey[DependencyFilter]("Filter for including modules containing avro dependencies.") + val avroEnableDecimalLogicalType = settingKey[Boolean]("Use java.math.BigDecimal instead of java.nio.ByteBuffer for logical type decimal.") + val avroFieldVisibility = settingKey[String]("Field visibility for the properties. Possible values: private, public.") + val avroOptionalGetters = settingKey[Boolean]("Generate getters that return Optional for nullable fields.") + val avroSpecificRecords = settingKey[Seq[String]]("List of fully qualified Avro record class names to recompile with current avro version and settings. Classes must be part of the Avro library dependencies scope.") + val avroSource = settingKey[File]("Default Avro source directory for *.avsc, *.avdl and *.avpr files.") + val avroStringType = settingKey[String]("Type for representing strings. Possible values: CharSequence, String, Utf8.") + val avroUnmanagedSourceDirectories = settingKey[Seq[File]]("Unmanaged Avro source directories, which contain manually created sources.") + val avroUseNamespace = settingKey[Boolean]("Validate that directory layout reflects namespaces, i.e. com/myorg/MyRecord.avsc.") + val avroVersion = settingKey[String]("Avro version to use in the project.") + + val avroGenerate = taskKey[Seq[File]]("Generate Java sources for Avro schemas.") + val avroUnpackDependencies = taskKey[Seq[File]]("Unpack avro dependencies.") + val packageAvro = taskKey[File]("Produces an avro artifact, such as a jar containing avro schemas.") + // format: on + + lazy val avroArtifactTasks: Seq[TaskKey[File]] = Seq(Compile, Test).map(_ / packageAvro) + + lazy val defaultSettings: Seq[Setting[_]] = Seq( + // compiler + avroCompiler := "com.github.sbt.avro.AvroCompilerBridge", + avroCreateSetters := true, + avroEnableDecimalLogicalType := true, + avroFieldVisibility := "public", + avroOptionalGetters := false, + avroStringType := "CharSequence", + avroUseNamespace := false, + + // dependency management + avroDependencyIncludeFilter := artifactFilter( + `type` = Artifact.SourceType, + classifier = AvroClassifier + ), + // addArtifact doesn't take publishArtifact setting in account + artifacts ++= Classpaths.artifactDefs(avroArtifactTasks).value, + packagedArtifacts ++= Classpaths.packaged(avroArtifactTasks).value, + // use a custom folders to avoid potential conflict with other generators + avroUnpackDependencies / target := sourceManaged.value / "avro", + avroGenerate / target := sourceManaged.value / "compiled_avro", + // setup avro configuration. Use library management to fetch the compiler + ivyConfigurations ++= Seq(Avro), + avroVersion := "1.12.0", + avroAdditionalDependencies := Seq( + "com.github.sbt" % "sbt-avro-compiler-bridge" % BuildInfo.version % Avro, + "org.apache.avro" % "avro-compiler" % avroVersion.value % Avro, + "org.apache.avro" % "avro" % avroVersion.value + ), + libraryDependencies ++= avroAdditionalDependencies.value + ) + + // settings to be applied for both Compile and Test + lazy val configScopedSettings: Seq[Setting[_]] = Seq( + avroSource := sourceDirectory.value / "avro", + avroUnmanagedSourceDirectories := Seq(avroSource.value), + avroSpecificRecords := Seq.empty, + // dependencies + avroUnpackDependencies / includeFilter := AllPassFilter, + avroUnpackDependencies / excludeFilter := HiddenFileFilter, + avroUnpackDependencies / target := configSrcSub(avroUnpackDependencies / target).value, + avroUnpackDependencies := unpackDependenciesTask(avroUnpackDependencies).value, + // source generation + avroGenerate / target := configSrcSub(avroGenerate / target).value, + managedSourceDirectories += (avroGenerate / target).value, + avroGenerate := sourceGeneratorTask(avroGenerate).dependsOn(avroUnpackDependencies).value, + sourceGenerators += avroGenerate.taskValue, + compile := compile.dependsOn(avroGenerate).value, + // packaging + packageAvro / artifactClassifier := Some(AvroClassifier), + packageAvro / publishArtifact := false + ) ++ packageTaskSettings(packageAvro, packageAvroMappings) ++ Seq( + packageAvro / artifact := (packageAvro / artifact).value.withType(Artifact.SourceType) + ) + } + + import autoImport._ + + def packageAvroMappings: Def.Initialize[Task[Seq[(File, String)]]] = Def.task { + avroUnmanagedSourceDirectories.value.flatMap(src => (src ** AvroFilter).pair(relativeTo(src))) + } + + override def trigger: PluginTrigger = noTrigger + + override def requires: Plugins = sbt.plugins.JvmPlugin + + override lazy val projectSettings: Seq[Setting[_]] = defaultSettings ++ + inConfig(Avro)(Defaults.configSettings) ++ + Seq(Compile, Test).flatMap(c => inConfig(c)(configScopedSettings)) + + private def unpack( + cacheBaseDirectory: File, + deps: Seq[File], + extractTarget: File, + includeFilter: FileFilter, + excludeFilter: FileFilter, + streams: TaskStreams + ): Seq[File] = { + def cachedExtractDep(jar: File): Seq[File] = { + val cached = FileFunction.cached( + cacheBaseDirectory / jar.name, + inStyle = FilesInfo.lastModified, + outStyle = FilesInfo.exists + ) { deps => + IO.createDirectory(extractTarget) + deps.flatMap { dep => + val filter = includeFilter -- excludeFilter + val (avroSpecs, filtered) = IO + .unzip(dep, extractTarget, AvroFilter) + .partition(filter.accept) + IO.delete(filtered) + if (avroSpecs.nonEmpty) { + streams.log.info("Extracted from " + dep + avroSpecs.mkString(":\n * ", "\n * ", "")) + } else { + streams.log.info(s"No Avro specification extracted from $dep") + } + avroSpecs + } + } + cached(Set(jar)).toSeq + } + + deps.flatMap(cachedExtractDep) + } + + private def unpackDependenciesTask(key: TaskKey[Seq[File]]) = Def.task { + val cacheBaseDirectory = Defaults.makeCrossTarget( + streams.value.cacheDirectory, + scalaBinaryVersion.value, + (pluginCrossBuild / sbtBinaryVersion).value, + sbtPlugin.value, + crossPaths.value + ) + val conf = configuration.value.toConfigRef + val avroArtifacts = update.value + .filter(avroDependencyIncludeFilter.value) + .toSeq + .collect { case (`conf`, _, _, file) => file } + + unpack( + cacheBaseDirectory = cacheBaseDirectory, + deps = avroArtifacts, + extractTarget = (key / target).value, + includeFilter = (key / includeFilter).value, + excludeFilter = (key / excludeFilter).value, + streams = (key / streams).value + ) + } + + private def sourceGeneratorTask(key: TaskKey[Seq[File]]) = Def.task { + val out = (key / streams).value + val externalSrcDir = (avroUnpackDependencies / target).value + val srcDirs = avroUnmanagedSourceDirectories.value :+ externalSrcDir + val outDir = (key / target).value + + val cachedCompile = { + import sbt.util.CacheStoreFactory + import sbt.util.CacheImplicits._ + + val cacheStoreFactory = CacheStoreFactory(out.cacheDirectory / "avro") + val lastCache = { (action: Option[Set[File]] => Set[File]) => + Tracked + .lastOutput[Unit, Set[File]](cacheStoreFactory.make("last-cache")) { case (_, l) => + action(l) + } + .apply(()) + } + val inCache = Difference.inputs(cacheStoreFactory.make("in-cache"), FileInfo.lastModified) + val outCache = Difference.outputs(cacheStoreFactory.make("out-cache"), FileInfo.exists) + + (inputs: Set[File], records: Seq[String]) => + lastCache { lastCache => + inCache(inputs) { inReport => + outCache { outReport => + if ( + (lastCache.isEmpty && records.nonEmpty) || inReport.modified.nonEmpty || outReport.modified.nonEmpty + ) { + // compile if + // - no previous cache and we have records to recompile + // - input files have changed + // - output files are missing + + // TODO Cache class loader + val avroClassLoader = new URLClassLoader( + "AvroClassLoader", + (Avro / dependencyClasspath).value.map(_.data.toURI.toURL).toArray, + this.getClass.getClassLoader + ) + + val compiler = avroClassLoader + .loadClass(avroCompiler.value) + .getDeclaredConstructor() + .newInstance() + .asInstanceOf[AvroCompiler] + + compiler.setStringType(avroStringType.value) + compiler.setFieldVisibility(avroFieldVisibility.value.toUpperCase) + compiler.setUseNamespace(avroUseNamespace.value) + compiler.setEnableDecimalLogicalType(avroEnableDecimalLogicalType.value) + compiler.setCreateSetters(avroOptionalGetters.value) + compiler.setOptionalGetters(avroCreateSetters.value) + + try { + val recs = records.map(avroClassLoader.loadClass) + val avdls = srcDirs.flatMap(d => (d ** AvroAvdlFilter).get) + val avscs = srcDirs.flatMap(d => + (d ** AvroAvscFilter).get.map(avsc => + new AvroFileRef(d, avsc.relativeTo(d).get.toString) + ) + ) + val avprs = srcDirs.flatMap(d => (d ** AvroAvrpFilter).get) + + out.log.info( + s"Avro compiler ${avroVersion.value} using stringType=${avroStringType.value}" + ) + + compiler.recompile(recs.toArray, outDir) + compiler.compileIdls(avdls.toArray, outDir) + compiler.compileAvscs(avscs.toArray, outDir) + compiler.compileAvprs(avprs.toArray, outDir) + + (outDir ** SbtAvro.JavaFileFilter).get.toSet + } finally { + avroClassLoader.close() + } + } else { + outReport.checked + } + } + } + } + } + + cachedCompile((srcDirs ** AvroFilter).get.toSet, avroSpecificRecords.value).toSeq + } + +} diff --git a/plugin/src/sbt-test/sbt-avro/avscparser/build.sbt b/plugin/src/sbt-test/sbt-avro/avscparser/build.sbt new file mode 100644 index 0000000..c9129df --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/avscparser/build.sbt @@ -0,0 +1,20 @@ + + +lazy val parser = project + .in(file("parser")) + .settings( + crossPaths := false, + autoScalaLibrary := false, + libraryDependencies ++= Seq( + "com.github.sbt" % "sbt-avro-compiler-bridge" % sys.props("plugin.version"), + "org.apache.avro" % "avro-compiler" % "1.12.0" + ) + ) + +lazy val root = project + .in(file(".")) + .enablePlugins(SbtAvro) + .dependsOn(parser % "avro") + .settings( + avroCompiler := "com.github.sbt.avro.CustomAvroCompiler" + ) \ No newline at end of file diff --git a/plugin/src/sbt-test/sbt-avro/avscparser/parser/src/main/java/com/github/sbt/avro/CustomAvroCompiler.java b/plugin/src/sbt-test/sbt-avro/avscparser/parser/src/main/java/com/github/sbt/avro/CustomAvroCompiler.java new file mode 100644 index 0000000..4a8d578 --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/avscparser/parser/src/main/java/com/github/sbt/avro/CustomAvroCompiler.java @@ -0,0 +1,23 @@ +package com.github.sbt.avro; + +import org.apache.avro.Schema; +import org.apache.avro.SchemaBuilder; +import org.apache.avro.NameValidator; + +import java.util.Collections; + +public class CustomAvroCompiler extends AvroCompilerBridge { + + @Override + protected Schema.Parser createParser() { + Schema.Parser parser = new Schema.Parser(); + parser.setValidateDefaults(false); + Schema externalSchema = SchemaBuilder + .enumeration("B") + .namespace("com.github.sbt.avro.test") + .symbols("B1"); + parser.addTypes(Collections.singletonList(externalSchema)); + return parser; + } + +} diff --git a/src/sbt-test/sbt-avro/avscparser/project/build.properties b/plugin/src/sbt-test/sbt-avro/avscparser/project/build.properties similarity index 100% rename from src/sbt-test/sbt-avro/avscparser/project/build.properties rename to plugin/src/sbt-test/sbt-avro/avscparser/project/build.properties diff --git a/src/sbt-test/sbt-avro/basic_current/project/plugins.sbt b/plugin/src/sbt-test/sbt-avro/avscparser/project/plugins.sbt similarity index 80% rename from src/sbt-test/sbt-avro/basic_current/project/plugins.sbt rename to plugin/src/sbt-test/sbt-avro/avscparser/project/plugins.sbt index 851cc08..c52f4d5 100644 --- a/src/sbt-test/sbt-avro/basic_current/project/plugins.sbt +++ b/plugin/src/sbt-test/sbt-avro/avscparser/project/plugins.sbt @@ -3,5 +3,3 @@ sys.props.get("plugin.version") match { case _ => sys.error("""|The system property 'plugin.version' is not defined. |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) } - -libraryDependencies += "org.apache.avro" % "avro-compiler" % "1.12.0" diff --git a/plugin/src/sbt-test/sbt-avro/avscparser/src/main/avro/a.avsc b/plugin/src/sbt-test/sbt-avro/avscparser/src/main/avro/a.avsc new file mode 100644 index 0000000..febe393 --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/avscparser/src/main/avro/a.avsc @@ -0,0 +1,16 @@ +{ + "name": "A", + "namespace": "com.github.sbt.avro.test", + "type": "record", + "fields": [ + { + "name": "supportsCustomRegisteredType", + "type": "B" + }, + { + "name": "invalidDefaultValue", + "type": "boolean", + "default": "invalid default value" + } + ] +} diff --git a/plugin/src/sbt-test/sbt-avro/avscparser/test b/plugin/src/sbt-test/sbt-avro/avscparser/test new file mode 100644 index 0000000..6a604b0 --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/avscparser/test @@ -0,0 +1 @@ +> avroGenerate diff --git a/plugin/src/sbt-test/sbt-avro/basic/build.sbt b/plugin/src/sbt-test/sbt-avro/basic/build.sbt new file mode 100644 index 0000000..783479a --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/basic/build.sbt @@ -0,0 +1,17 @@ +ThisBuild / scalaVersion := "2.13.11" + +lazy val basic = crossProject( + Avro("1.12.0"), + Avro("1.11.3"), + Avro("1.10.0"), + Avro("1.9.2"), + Avro("1.8.2"), + ) + .crossType(CrossType.Pure) + .in(file(".")) + .avroSettings("1.9.2")( + libraryDependencies += "joda-time" % "joda-time" % "2.12.7" + ) + .avroSettings("1.8.2")( + libraryDependencies += "joda-time" % "joda-time" % "2.12.7" + ) diff --git a/plugin/src/sbt-test/sbt-avro/basic/project/Avro.scala b/plugin/src/sbt-test/sbt-avro/basic/project/Avro.scala new file mode 100644 index 0000000..eefba9a --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/basic/project/Avro.scala @@ -0,0 +1,24 @@ +import sbt._ +import Keys._ +import sbtcrossproject._ +import com.github.sbt.avro.SbtAvro + +case class Avro(version: String) extends Platform { + + import CrossPlugin.autoImport._ + import SbtAvro.autoImport._ + + def identifier: String = "avro-" + version.split('.')(1) + def sbtSuffix: String = "Avro_" + version.split('.')(1) + def enable(project: Project): Project = project + .enablePlugins(SbtAvro) + .settings( + avroVersion := version, + Compile / avroUnmanagedSourceDirectories ++= crossProjectCrossType.value + .sharedSrcDir(baseDirectory.value, "main") + .map(_.getParentFile / "avro"), + Test / avroUnmanagedSourceDirectories ++= crossProjectCrossType.value + .sharedSrcDir(baseDirectory.value, "test") + .map(_.getParentFile / "avro"), + ) +} diff --git a/plugin/src/sbt-test/sbt-avro/basic/project/AvroCrossPlugin.scala b/plugin/src/sbt-test/sbt-avro/basic/project/AvroCrossPlugin.scala new file mode 100644 index 0000000..dbe99c1 --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/basic/project/AvroCrossPlugin.scala @@ -0,0 +1,25 @@ +import scala.language.implicitConversions + +import sbt._ +import sbtcrossproject._ + +object AvroCrossPlugin extends sbt.AutoPlugin { + object autoImport { + + implicit def AvroCrossProjectBuilderOps(builder: CrossProject.Builder): AvroCrossProjectOps = + new AvroCrossProjectOps(builder.crossType(CrossType.Full)) + + implicit class AvroCrossProjectOps(project: CrossProject) { + def avro(version: String): Project = project.projects(Avro(version)) + + def avroSettings(version: String)(ss: Def.SettingsDefinition*): CrossProject = + avroConfigure(version)(_.settings(ss: _*)) + + def avroEnablePlugins(version: String)(plugins: Plugins*): CrossProject = + avroConfigure(version)(_.enablePlugins(plugins: _*)) + + def avroConfigure(version: String)(transformer: Project => Project): CrossProject = + project.configurePlatform(Avro(version))(transformer) + } + } +} diff --git a/src/sbt-test/sbt-avro/basic_current/project/build.properties b/plugin/src/sbt-test/sbt-avro/basic/project/build.properties similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/project/build.properties rename to plugin/src/sbt-test/sbt-avro/basic/project/build.properties diff --git a/src/sbt-test/sbt-avro/publishing/project/plugins.sbt b/plugin/src/sbt-test/sbt-avro/basic/project/plugins.sbt similarity index 80% rename from src/sbt-test/sbt-avro/publishing/project/plugins.sbt rename to plugin/src/sbt-test/sbt-avro/basic/project/plugins.sbt index 851cc08..a97f6e3 100644 --- a/src/sbt-test/sbt-avro/publishing/project/plugins.sbt +++ b/plugin/src/sbt-test/sbt-avro/basic/project/plugins.sbt @@ -4,4 +4,4 @@ sys.props.get("plugin.version") match { |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) } -libraryDependencies += "org.apache.avro" % "avro-compiler" % "1.12.0" +addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "1.3.2") diff --git a/src/test/resources/avro/_a.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/_a.avsc similarity index 100% rename from src/test/resources/avro/_a.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/_a.avsc diff --git a/src/test/resources/avro/_b.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/_b.avsc similarity index 100% rename from src/test/resources/avro/_b.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/_b.avsc diff --git a/src/test/resources/avro/_c.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/_c.avsc similarity index 100% rename from src/test/resources/avro/_c.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/_c.avsc diff --git a/src/test/resources/avro/_d.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/_d.avsc similarity index 100% rename from src/test/resources/avro/_d.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/_d.avsc diff --git a/src/test/resources/avro/_e.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/_e.avsc similarity index 100% rename from src/test/resources/avro/_e.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/_e.avsc diff --git a/src/test/resources/avro/a.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/a.avsc similarity index 100% rename from src/test/resources/avro/a.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/a.avsc diff --git a/src/test/resources/avro/b.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/b.avsc similarity index 100% rename from src/test/resources/avro/b.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/b.avsc diff --git a/src/test/resources/avro/c.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/c.avsc similarity index 100% rename from src/test/resources/avro/c.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/c.avsc diff --git a/src/test/resources/avro/d.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/d.avsc similarity index 100% rename from src/test/resources/avro/d.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/d.avsc diff --git a/src/test/resources/avro/e.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/e.avsc similarity index 100% rename from src/test/resources/avro/e.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/e.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/logicalType.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/main/avro/logicalType.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/main/avro/logicalType.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/main/avro/logicalType.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/test/avro/x.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/test/avro/x.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/test/avro/x.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/test/avro/x.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/test/avro/y.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/test/avro/y.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/test/avro/y.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/test/avro/y.avsc diff --git a/src/sbt-test/sbt-avro/basic_current/src/test/avro/z.avsc b/plugin/src/sbt-test/sbt-avro/basic/src/test/avro/z.avsc similarity index 100% rename from src/sbt-test/sbt-avro/basic_current/src/test/avro/z.avsc rename to plugin/src/sbt-test/sbt-avro/basic/src/test/avro/z.avsc diff --git a/plugin/src/sbt-test/sbt-avro/basic/test b/plugin/src/sbt-test/sbt-avro/basic/test new file mode 100644 index 0000000..d23c116 --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/basic/test @@ -0,0 +1,39 @@ +> avroGenerate + +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/A.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/B.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/C.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/D.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/E.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/_A.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/_B.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/_C.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/_D.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/_E.java + +$ exists .avro-11/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/A.java +$ exists .avro-10/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/A.java +$ exists .avro-9/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/A.java +$ exists .avro-8/target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/A.java + +> compile + +$ exists .avro-12/target/scala-2.13/classes/com/github/sbt/avro/test/A.class +$ exists .avro-12/target/scala-2.13/classes/com/github/sbt/avro/test/B.class +$ exists .avro-12/target/scala-2.13/classes/com/github/sbt/avro/test/C.class +$ exists .avro-12/target/scala-2.13/classes/com/github/sbt/avro/test/D.class +$ exists .avro-12/target/scala-2.13/classes/com/github/sbt/avro/test/E.class +$ exists .avro-12/target/scala-2.13/classes/com/github/sbt/avro/test/_A.class +$ exists .avro-12/target/scala-2.13/classes/com/github/sbt/avro/test/_B.class +$ exists .avro-12/target/scala-2.13/classes/com/github/sbt/avro/test/_C.class +$ exists .avro-12/target/scala-2.13/classes/com/github/sbt/avro/test/_D.class +$ exists .avro-12/target/scala-2.13/classes/com/github/sbt/avro/test/_E.class + +> Test/compile + +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/test/com/github/sbt/avro/test/X.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/test/com/github/sbt/avro/test/Y.java +$ exists .avro-12/target/scala-2.13/src_managed/compiled_avro/test/com/github/sbt/avro/test/Z.java +$ exists .avro-12/target/scala-2.13/test-classes/com/github/sbt/avro/test/X.class +$ exists .avro-12/target/scala-2.13/test-classes/com/github/sbt/avro/test/Y.class +$ exists .avro-12/target/scala-2.13/test-classes/com/github/sbt/avro/test/Z.class diff --git a/src/sbt-test/sbt-avro/publishing/build.sbt b/plugin/src/sbt-test/sbt-avro/publishing/build.sbt similarity index 90% rename from src/sbt-test/sbt-avro/publishing/build.sbt rename to plugin/src/sbt-test/sbt-avro/publishing/build.sbt index 73f3034..7aff966 100644 --- a/src/sbt-test/sbt-avro/publishing/build.sbt +++ b/plugin/src/sbt-test/sbt-avro/publishing/build.sbt @@ -1,10 +1,6 @@ lazy val commonSettings = Seq( organization := "com.github.sbt", - publishTo := Some(Opts.resolver.sonatypeReleases), - scalaVersion := "2.13.15", - libraryDependencies ++= Seq( - "org.apache.avro" % "avro" % avroCompilerVersion - ) + scalaVersion := "2.13.15" ) lazy val javaOnlySettings = Seq( @@ -15,6 +11,7 @@ lazy val javaOnlySettings = Seq( lazy val `external`: Project = project .in(file("external")) + .enablePlugins(SbtAvro) .settings(commonSettings) .settings(javaOnlySettings) .settings( @@ -28,6 +25,7 @@ lazy val `external`: Project = project lazy val `transitive`: Project = project .in(file("transitive")) + .enablePlugins(SbtAvro) .settings(commonSettings) .settings(javaOnlySettings) .settings( @@ -42,6 +40,7 @@ lazy val `transitive`: Project = project lazy val root: Project = project .in(file(".")) + .enablePlugins(SbtAvro) .settings(commonSettings) .settings( name := "publishing-test", diff --git a/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avdl.avdl b/plugin/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avdl.avdl similarity index 100% rename from src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avdl.avdl rename to plugin/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avdl.avdl diff --git a/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avpr.avpr b/plugin/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avpr.avpr similarity index 100% rename from src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avpr.avpr rename to plugin/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avpr.avpr diff --git a/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avsc.avsc b/plugin/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avsc.avsc similarity index 100% rename from src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avsc.avsc rename to plugin/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/avsc.avsc diff --git a/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/exclude.avsc b/plugin/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/exclude.avsc similarity index 100% rename from src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/exclude.avsc rename to plugin/src/sbt-test/sbt-avro/publishing/external/src/main/avro/com/github/sbt/avro/test/external/exclude.avsc diff --git a/src/sbt-test/sbt-avro/publishing/project/build.properties b/plugin/src/sbt-test/sbt-avro/publishing/project/build.properties similarity index 100% rename from src/sbt-test/sbt-avro/publishing/project/build.properties rename to plugin/src/sbt-test/sbt-avro/publishing/project/build.properties diff --git a/src/sbt-test/sbt-avro/avscparser/project/plugins.sbt b/plugin/src/sbt-test/sbt-avro/publishing/project/plugins.sbt similarity index 80% rename from src/sbt-test/sbt-avro/avscparser/project/plugins.sbt rename to plugin/src/sbt-test/sbt-avro/publishing/project/plugins.sbt index 851cc08..c52f4d5 100644 --- a/src/sbt-test/sbt-avro/avscparser/project/plugins.sbt +++ b/plugin/src/sbt-test/sbt-avro/publishing/project/plugins.sbt @@ -3,5 +3,3 @@ sys.props.get("plugin.version") match { case _ => sys.error("""|The system property 'plugin.version' is not defined. |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) } - -libraryDependencies += "org.apache.avro" % "avro-compiler" % "1.12.0" diff --git a/src/sbt-test/sbt-avro/publishing/src/main/scala/com/github/sbt/avro/test/Main.scala b/plugin/src/sbt-test/sbt-avro/publishing/src/main/scala/com/github/sbt/avro/test/Main.scala similarity index 100% rename from src/sbt-test/sbt-avro/publishing/src/main/scala/com/github/sbt/avro/test/Main.scala rename to plugin/src/sbt-test/sbt-avro/publishing/src/main/scala/com/github/sbt/avro/test/Main.scala diff --git a/src/sbt-test/sbt-avro/publishing/src/test/scala/com/github/sbt/avro/test/Test.scala b/plugin/src/sbt-test/sbt-avro/publishing/src/test/scala/com/github/sbt/avro/test/Test.scala similarity index 100% rename from src/sbt-test/sbt-avro/publishing/src/test/scala/com/github/sbt/avro/test/Test.scala rename to plugin/src/sbt-test/sbt-avro/publishing/src/test/scala/com/github/sbt/avro/test/Test.scala diff --git a/src/sbt-test/sbt-avro/publishing/test b/plugin/src/sbt-test/sbt-avro/publishing/test similarity index 100% rename from src/sbt-test/sbt-avro/publishing/test rename to plugin/src/sbt-test/sbt-avro/publishing/test diff --git a/src/sbt-test/sbt-avro/publishing/transitive/src/main/avro/com/github/sbt/avro/test/transitive/avsc.avsc b/plugin/src/sbt-test/sbt-avro/publishing/transitive/src/main/avro/com/github/sbt/avro/test/transitive/avsc.avsc similarity index 100% rename from src/sbt-test/sbt-avro/publishing/transitive/src/main/avro/com/github/sbt/avro/test/transitive/avsc.avsc rename to plugin/src/sbt-test/sbt-avro/publishing/transitive/src/main/avro/com/github/sbt/avro/test/transitive/avsc.avsc diff --git a/src/sbt-test/sbt-avro/publishing/transitive/src/test/resources/test.avsc b/plugin/src/sbt-test/sbt-avro/publishing/transitive/src/test/resources/test.avsc similarity index 100% rename from src/sbt-test/sbt-avro/publishing/transitive/src/test/resources/test.avsc rename to plugin/src/sbt-test/sbt-avro/publishing/transitive/src/test/resources/test.avsc diff --git a/src/sbt-test/sbt-avro/recompile/build.sbt b/plugin/src/sbt-test/sbt-avro/recompile/build.sbt similarity index 50% rename from src/sbt-test/sbt-avro/recompile/build.sbt rename to plugin/src/sbt-test/sbt-avro/recompile/build.sbt index dc32aac..7551a20 100644 --- a/src/sbt-test/sbt-avro/recompile/build.sbt +++ b/plugin/src/sbt-test/sbt-avro/recompile/build.sbt @@ -1,7 +1,10 @@ +enablePlugins(SbtAvro) + name := "recompile-test" scalaVersion := "2.13.11" libraryDependencies ++= Seq( - "org.apache.avro" % "avro" % avroCompilerVersion, + // depend on test jar to get some generated records in the build + "org.apache.avro" % "avro" % avroVersion.value % "avro" classifier "tests", "org.specs2" %% "specs2-core" % "4.20.9" % Test ) @@ -9,4 +12,4 @@ avroStringType := "String" avroFieldVisibility := "public" avroOptionalGetters := true avroEnableDecimalLogicalType := false -Compile / avroSpecificRecords += classOf[org.apache.avro.specific.TestRecordWithLogicalTypes] +Compile / avroSpecificRecords += "org.apache.avro.specific.TestRecordWithLogicalTypes" diff --git a/src/sbt-test/sbt-avro/recompile/project/build.properties b/plugin/src/sbt-test/sbt-avro/recompile/project/build.properties similarity index 100% rename from src/sbt-test/sbt-avro/recompile/project/build.properties rename to plugin/src/sbt-test/sbt-avro/recompile/project/build.properties diff --git a/plugin/src/sbt-test/sbt-avro/recompile/project/plugins.sbt b/plugin/src/sbt-test/sbt-avro/recompile/project/plugins.sbt new file mode 100644 index 0000000..82d3c49 --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/recompile/project/plugins.sbt @@ -0,0 +1,6 @@ +sys.props.get("plugin.version") match { + case Some(x) => + addSbtPlugin("com.github.sbt" % "sbt-avro" % x) + case _ => sys.error("""|The system property 'plugin.version' is not defined. + |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) +} diff --git a/src/sbt-test/sbt-avro/recompile/record.avsc b/plugin/src/sbt-test/sbt-avro/recompile/record.avsc similarity index 100% rename from src/sbt-test/sbt-avro/recompile/record.avsc rename to plugin/src/sbt-test/sbt-avro/recompile/record.avsc diff --git a/src/sbt-test/sbt-avro/recompile/test b/plugin/src/sbt-test/sbt-avro/recompile/test similarity index 100% rename from src/sbt-test/sbt-avro/recompile/test rename to plugin/src/sbt-test/sbt-avro/recompile/test diff --git a/plugin/src/sbt-test/sbt-avro/sbt_1.3/build.sbt b/plugin/src/sbt-test/sbt-avro/sbt_1.3/build.sbt new file mode 100644 index 0000000..391e135 --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/sbt_1.3/build.sbt @@ -0,0 +1,5 @@ +enablePlugins(SbtAvro) + +name := "basic-test" +scalaVersion := "2.13.11" +libraryDependencies += "org.apache.avro" % "avro" % avroVersion.value diff --git a/src/sbt-test/sbt-avro/sbt_1.3/project/build.properties b/plugin/src/sbt-test/sbt-avro/sbt_1.3/project/build.properties similarity index 100% rename from src/sbt-test/sbt-avro/sbt_1.3/project/build.properties rename to plugin/src/sbt-test/sbt-avro/sbt_1.3/project/build.properties diff --git a/src/sbt-test/sbt-avro/sbt_1.3/project/plugins.sbt b/plugin/src/sbt-test/sbt-avro/sbt_1.3/project/plugins.sbt similarity index 80% rename from src/sbt-test/sbt-avro/sbt_1.3/project/plugins.sbt rename to plugin/src/sbt-test/sbt-avro/sbt_1.3/project/plugins.sbt index 851cc08..c52f4d5 100644 --- a/src/sbt-test/sbt-avro/sbt_1.3/project/plugins.sbt +++ b/plugin/src/sbt-test/sbt-avro/sbt_1.3/project/plugins.sbt @@ -3,5 +3,3 @@ sys.props.get("plugin.version") match { case _ => sys.error("""|The system property 'plugin.version' is not defined. |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) } - -libraryDependencies += "org.apache.avro" % "avro-compiler" % "1.12.0" diff --git a/plugin/src/sbt-test/sbt-avro/sbt_1.3/src b/plugin/src/sbt-test/sbt-avro/sbt_1.3/src new file mode 120000 index 0000000..12c7604 --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/sbt_1.3/src @@ -0,0 +1 @@ +../basic/src \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.11/test b/plugin/src/sbt-test/sbt-avro/sbt_1.3/test similarity index 87% rename from src/sbt-test/sbt-avro/basic_1.11/test rename to plugin/src/sbt-test/sbt-avro/sbt_1.3/test index 90dc1d6..f410585 100644 --- a/src/sbt-test/sbt-avro/basic_1.11/test +++ b/plugin/src/sbt-test/sbt-avro/sbt_1.3/test @@ -1,4 +1,3 @@ -> set avroSchemaParserBuilder := com.github.sbt.avro.LegacySchemaParserBuilder(validateDefaults = false) > avroGenerate $ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/A.java @@ -33,10 +32,3 @@ $ exists target/scala-2.13/src_managed/compiled_avro/test/com/github/sbt/avro/te $ exists target/scala-2.13/test-classes/com/github/sbt/avro/test/X.class $ exists target/scala-2.13/test-classes/com/github/sbt/avro/test/Y.class $ exists target/scala-2.13/test-classes/com/github/sbt/avro/test/Z.class - -> clean - -> set avroSchemaParserBuilder := com.github.sbt.avro.LegacySchemaParserBuilder(validateDefaults = true) - -# should fail because f.avsc has invalid default value --> avroGenerate diff --git a/src/sbt-test/sbt-avro/settings/build.sbt b/plugin/src/sbt-test/sbt-avro/settings/build.sbt similarity index 60% rename from src/sbt-test/sbt-avro/settings/build.sbt rename to plugin/src/sbt-test/sbt-avro/settings/build.sbt index 4a93ea5..5164008 100644 --- a/src/sbt-test/sbt-avro/settings/build.sbt +++ b/plugin/src/sbt-test/sbt-avro/settings/build.sbt @@ -1,7 +1,10 @@ +enablePlugins(SbtAvro) + name := "settings-test" scalaVersion := "2.13.11" libraryDependencies ++= Seq( - "org.apache.avro" % "avro" % avroCompilerVersion, + // depend on test jar to get some generated records in the build + "org.apache.avro" % "avro" % avroVersion.value % "avro" classifier "tests", "org.specs2" %% "specs2-core" % "4.20.9" % Test ) @@ -9,6 +12,6 @@ avroStringType := "String" avroFieldVisibility := "public" avroOptionalGetters := true avroEnableDecimalLogicalType := false -Compile / avroSpecificRecords += classOf[org.apache.avro.specific.TestRecordWithLogicalTypes] +Compile / avroSpecificRecords += "org.apache.avro.specific.TestRecordWithLogicalTypes" Compile / avroSource := (Compile / sourceDirectory).value / "avro_source" Compile / avroGenerate / target := (Compile / sourceManaged).value diff --git a/src/sbt-test/sbt-avro/settings/project/build.properties b/plugin/src/sbt-test/sbt-avro/settings/project/build.properties similarity index 100% rename from src/sbt-test/sbt-avro/settings/project/build.properties rename to plugin/src/sbt-test/sbt-avro/settings/project/build.properties diff --git a/plugin/src/sbt-test/sbt-avro/settings/project/plugins.sbt b/plugin/src/sbt-test/sbt-avro/settings/project/plugins.sbt new file mode 100644 index 0000000..c52f4d5 --- /dev/null +++ b/plugin/src/sbt-test/sbt-avro/settings/project/plugins.sbt @@ -0,0 +1,5 @@ +sys.props.get("plugin.version") match { + case Some(x) => addSbtPlugin("com.github.sbt" % "sbt-avro" % x) + case _ => sys.error("""|The system property 'plugin.version' is not defined. + |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) +} diff --git a/src/sbt-test/sbt-avro/settings/src/main/avro_source/avdl.avdl b/plugin/src/sbt-test/sbt-avro/settings/src/main/avro_source/avdl.avdl similarity index 100% rename from src/sbt-test/sbt-avro/settings/src/main/avro_source/avdl.avdl rename to plugin/src/sbt-test/sbt-avro/settings/src/main/avro_source/avdl.avdl diff --git a/src/sbt-test/sbt-avro/settings/src/main/avro_source/avpr.avpr b/plugin/src/sbt-test/sbt-avro/settings/src/main/avro_source/avpr.avpr similarity index 100% rename from src/sbt-test/sbt-avro/settings/src/main/avro_source/avpr.avpr rename to plugin/src/sbt-test/sbt-avro/settings/src/main/avro_source/avpr.avpr diff --git a/src/sbt-test/sbt-avro/settings/src/main/avro_source/avsc.avsc b/plugin/src/sbt-test/sbt-avro/settings/src/main/avro_source/avsc.avsc similarity index 100% rename from src/sbt-test/sbt-avro/settings/src/main/avro_source/avsc.avsc rename to plugin/src/sbt-test/sbt-avro/settings/src/main/avro_source/avsc.avsc diff --git a/src/sbt-test/sbt-avro/settings/src/test/scala/com/github/sbt/avro/test/settings/SettingsSpec.scala b/plugin/src/sbt-test/sbt-avro/settings/src/test/scala/com/github/sbt/avro/test/settings/SettingsSpec.scala similarity index 100% rename from src/sbt-test/sbt-avro/settings/src/test/scala/com/github/sbt/avro/test/settings/SettingsSpec.scala rename to plugin/src/sbt-test/sbt-avro/settings/src/test/scala/com/github/sbt/avro/test/settings/SettingsSpec.scala diff --git a/src/sbt-test/sbt-avro/settings/test b/plugin/src/sbt-test/sbt-avro/settings/test similarity index 100% rename from src/sbt-test/sbt-avro/settings/test rename to plugin/src/sbt-test/sbt-avro/settings/test diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 93c6052..d0fc71c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -13,5 +13,6 @@ object Dependencies { object Test { val Specs2Core = "org.specs2" %% "specs2-core" % Versions.Specs2 % "test" + val AvroCompiler = "org.apache.avro" % "avro-compiler" % Versions.Avro % "test" } } diff --git a/project/plugins.sbt b/project/plugins.sbt index 519c935..493c3a1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,4 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.12.0") addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.24.0") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.8.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") diff --git a/src/main/java/com/github/sbt/avro/mojo/SchemaParserBuilder.java b/src/main/java/com/github/sbt/avro/mojo/SchemaParserBuilder.java deleted file mode 100644 index 394960e..0000000 --- a/src/main/java/com/github/sbt/avro/mojo/SchemaParserBuilder.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.sbt.avro.mojo; - -import org.apache.avro.Schema; - -public interface SchemaParserBuilder { - Schema.Parser build(); -} diff --git a/src/main/scala/com/github/sbt/avro/DefaultSchemaParserBuilder.scala b/src/main/scala/com/github/sbt/avro/DefaultSchemaParserBuilder.scala deleted file mode 100644 index 61d633a..0000000 --- a/src/main/scala/com/github/sbt/avro/DefaultSchemaParserBuilder.scala +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.sbt.avro - -import com.github.sbt.avro.mojo.SchemaParserBuilder -import org.apache.avro.Schema - -object DefaultSchemaParserBuilder { - - def default(): SchemaParserBuilder = { - val Array(1, minor, _) = - classOf[Schema].getPackage.getImplementationVersion.split("\\.").take(3).map(_.toInt) - if (minor >= 12) { - NameValidatorSchemaParserBuilder() - } else { - LegacySchemaParserBuilder() - } - } -} diff --git a/src/main/scala/com/github/sbt/avro/LegacySchemaParserBuilder.scala b/src/main/scala/com/github/sbt/avro/LegacySchemaParserBuilder.scala deleted file mode 100644 index 979dc29..0000000 --- a/src/main/scala/com/github/sbt/avro/LegacySchemaParserBuilder.scala +++ /dev/null @@ -1,46 +0,0 @@ -package com.github.sbt.avro - -import com.github.sbt.avro.mojo.SchemaParserBuilder -import org.apache.avro.Schema - -import scala.annotation.nowarn -import scala.collection.JavaConverters.* - -// used until avro 1.11 -// for avro 2.12+ use NameValidatorSchemaParserBuilder -case class LegacySchemaParserBuilder( - types: Iterable[Schema] = LegacySchemaParserBuilder.DefaultTypes, - validate: Boolean = LegacySchemaParserBuilder.DefaultValidate, - validateDefaults: Boolean = LegacySchemaParserBuilder.DefaultValidateDefaults -) extends SchemaParserBuilder { - - override def build(): Schema.Parser = { - val parser = new Schema.Parser - // addTypes(Map types) is the only API available in 1.8 - parser.addTypes(types.map(el => el.getFullName -> el).toMap.asJava): @nowarn - LegacySchemaParserBuilder.setValidate(parser)(validate) - parser.setValidateDefaults(validateDefaults) - parser - } -} - -object LegacySchemaParserBuilder { - // validate hase been removed in 1.12 in favor of a NameValidator - private def setValidate(parser: Schema.Parser)(validate: Boolean): Schema.Parser = - classOf[Schema.Parser] - .getMethod("setValidate", classOf[Boolean]) - .invoke(parser, validate: java.lang.Boolean) - .asInstanceOf[Schema.Parser] - - private def getValidate(parser: Schema.Parser): Boolean = - classOf[Schema.Parser] - .getMethod("getValidate") - .invoke(parser) - .asInstanceOf[Boolean] - - private val defaultParser = new Schema.Parser - - private val DefaultTypes: Iterable[Schema] = defaultParser.getTypes.values().asScala - private val DefaultValidate: Boolean = getValidate(defaultParser) - private val DefaultValidateDefaults: Boolean = defaultParser.getValidateDefaults -} diff --git a/src/main/scala/com/github/sbt/avro/NameValidatorSchemaParserBuilder.scala b/src/main/scala/com/github/sbt/avro/NameValidatorSchemaParserBuilder.scala deleted file mode 100644 index a253d20..0000000 --- a/src/main/scala/com/github/sbt/avro/NameValidatorSchemaParserBuilder.scala +++ /dev/null @@ -1,19 +0,0 @@ -package com.github.sbt.avro - -import com.github.sbt.avro.mojo.SchemaParserBuilder -import org.apache.avro.{NameValidator, Schema} - -import scala.collection.JavaConverters._ - -case class NameValidatorSchemaParserBuilder( - types: Iterable[Schema] = Iterable.empty, - validation: NameValidator = NameValidator.UTF_VALIDATOR, - validateDefaults: Boolean = true -) extends SchemaParserBuilder { - - override def build(): Schema.Parser = { - val parser = new Schema.Parser(validation) - parser.addTypes(types.asJava) - parser.setValidateDefaults(validateDefaults) - } -} diff --git a/src/main/scala/com/github/sbt/avro/SbtAvro.scala b/src/main/scala/com/github/sbt/avro/SbtAvro.scala deleted file mode 100644 index 1a537c3..0000000 --- a/src/main/scala/com/github/sbt/avro/SbtAvro.scala +++ /dev/null @@ -1,428 +0,0 @@ -package com.github.sbt.avro - -import org.apache.avro.Protocol -import org.apache.avro.compiler.idl.Idl -import org.apache.avro.compiler.specific.SpecificCompiler -import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility -import org.apache.avro.generic.GenericData.StringType -import sbt.Keys._ -import sbt._ -import CrossVersion.partialVersion -import Path.relativeTo -import com.github.sbt.avro.mojo.{AvroFileRef, AvscFilesCompiler, SchemaParserBuilder} -import org.apache.avro.specific.SpecificRecord -import sbt.librarymanagement.DependencyFilter - -import java.io.File -import java.util.jar.JarFile -import scala.collection.JavaConverters._ - -/** Simple plugin for generating the Java sources for Avro schemas and protocols. */ -object SbtAvro extends AutoPlugin { - - val AvroClassifier = "avro" - - // since 1.10, avro defines Implementation-Version in artifact manifest - private def implVersion = Option(classOf[SpecificCompiler].getPackage.getImplementationVersion) - - // avro defines OSGI Bundle-Version in artifact manifest - private def bundleVersion = { - val location = classOf[SpecificCompiler].getProtectionDomain.getCodeSource.getLocation - val jar = new JarFile(new File(location.toURI)) - jar.getManifest.getMainAttributes.getValue("Bundle-Version") - } - - private val AvroAvrpFilter: NameFilter = "*.avpr" - private val AvroAvdlFilter: NameFilter = "*.avdl" - private val AvroAvscFilter: NameFilter = "*.avsc" - private val AvroFilter: NameFilter = AvroAvscFilter | AvroAvdlFilter | AvroAvrpFilter - - private val JavaFileFilter: NameFilter = "*.java" - - object autoImport { - - import Defaults._ - - lazy val avroCompilerVersion: String = implVersion.getOrElse(bundleVersion) - - // format: off - val avroCreateSetters = settingKey[Boolean]("Generate setters. Default: true") - val avroDependencyIncludeFilter = settingKey[DependencyFilter]("Filter for including modules containing avro dependencies.") - val avroEnableDecimalLogicalType = settingKey[Boolean]("Use java.math.BigDecimal instead of java.nio.ByteBuffer for logical type \"decimal\". Default: true.") - val avroFieldVisibility = settingKey[String]("Field visibility for the properties. Possible values: private, public. Default: public.") - val avroIncludes = settingKey[Seq[File]]("Avro schema includes.") - val avroOptionalGetters = settingKey[Boolean]("Generate getters that return Optional for nullable fields. Default: false.") - val avroSpecificRecords = settingKey[Seq[Class[_ <: SpecificRecord]]]("List of avro records to recompile with current avro version and settings.") - val avroSchemaParserBuilder = settingKey[SchemaParserBuilder](".avsc schema parser builder") - val avroSource = settingKey[File]("Default Avro source directory.") - val avroStringType = settingKey[String]("Type for representing strings. Possible values: CharSequence, String, Utf8. Default: CharSequence.") - val avroUnpackDependencies = taskKey[Seq[File]]("Unpack avro dependencies.") - val avroUseNamespace = settingKey[Boolean]("Validate that directory layout reflects namespaces, i.e. src/main/avro/com/myorg/MyRecord.avsc. Default: false.") - - val avroGenerate = taskKey[Seq[File]]("Generate Java sources for Avro schemas.") - val packageAvro = taskKey[File]("Produces an avro artifact, such as a jar containing avro schemas.") - // format: on - - lazy val avroArtifactTasks: Seq[TaskKey[File]] = Seq(Compile, Test).map(_ / packageAvro) - - lazy val defaultSettings: Seq[Setting[_]] = Seq( - avroDependencyIncludeFilter := artifactFilter( - `type` = Artifact.SourceType, - classifier = AvroClassifier - ), - avroIncludes := Seq(), - // addArtifact doesn't take publishArtifact setting in account - artifacts ++= Classpaths.artifactDefs(avroArtifactTasks).value, - packagedArtifacts ++= Classpaths.packaged(avroArtifactTasks).value, - // use a custom folders to avoid potential conflict with other generators - avroUnpackDependencies / target := sourceManaged.value / "avro", - avroGenerate / target := sourceManaged.value / "compiled_avro" - ) - - // settings to be applied for both Compile and Test - lazy val configScopedSettings: Seq[Setting[_]] = Seq( - avroSource := sourceDirectory.value / "avro", - avroSpecificRecords := Seq.empty, - // dependencies - avroUnpackDependencies / includeFilter := AllPassFilter, - avroUnpackDependencies / excludeFilter := HiddenFileFilter, - avroUnpackDependencies / target := configSrcSub(avroUnpackDependencies / target).value, - avroUnpackDependencies := unpackDependenciesTask(avroUnpackDependencies).value, - // source generation - avroGenerate / target := configSrcSub(avroGenerate / target).value, - managedSourceDirectories += (avroGenerate / target).value, - avroGenerate := sourceGeneratorTask(avroGenerate).dependsOn(avroUnpackDependencies).value, - sourceGenerators += avroGenerate.taskValue, - compile := compile.dependsOn(avroGenerate).value, - // packaging - packageAvro / artifactClassifier := Some(AvroClassifier), - packageAvro / publishArtifact := false - ) ++ packageTaskSettings(packageAvro, packageAvroMappings) ++ Seq( - packageAvro / artifact := (packageAvro / artifact).value.withType(Artifact.SourceType) - ) - } - - import autoImport._ - - def packageAvroMappings = Def.task { - (avroSource.value ** AvroFilter) pair relativeTo(avroSource.value) - } - - override def trigger: PluginTrigger = allRequirements - - override def requires: Plugins = sbt.plugins.JvmPlugin - - override lazy val globalSettings: Seq[Setting[_]] = Seq( - avroStringType := "CharSequence", - avroFieldVisibility := "public", - avroEnableDecimalLogicalType := true, - avroUseNamespace := false, - avroOptionalGetters := false, - avroCreateSetters := true, - avroSchemaParserBuilder := DefaultSchemaParserBuilder.default() - ) - - override lazy val projectSettings: Seq[Setting[_]] = defaultSettings ++ - Seq(Compile, Test).flatMap(c => inConfig(c)(configScopedSettings)) - - private def unpack( - cacheBaseDirectory: File, - deps: Seq[File], - extractTarget: File, - includeFilter: FileFilter, - excludeFilter: FileFilter, - streams: TaskStreams - ): Seq[File] = { - def cachedExtractDep(jar: File): Seq[File] = { - val cached = FileFunction.cached( - cacheBaseDirectory / jar.name, - inStyle = FilesInfo.lastModified, - outStyle = FilesInfo.exists - ) { deps => - IO.createDirectory(extractTarget) - deps.flatMap { dep => - val filter = includeFilter -- excludeFilter - val (avroSpecs, filtered) = IO - .unzip(dep, extractTarget, AvroFilter) - .partition(filter.accept) - IO.delete(filtered) - if (avroSpecs.nonEmpty) { - streams.log.info("Extracted from " + dep + avroSpecs.mkString(":\n * ", "\n * ", "")) - } else { - streams.log.info(s"No Avro specification extracted from $dep") - } - avroSpecs - } - } - cached(Set(jar)).toSeq - } - - deps.flatMap(cachedExtractDep) - } - - private def unpackDependenciesTask(key: TaskKey[Seq[File]]) = Def.task { - val cacheBaseDirectory = Defaults.makeCrossTarget( - streams.value.cacheDirectory, - scalaBinaryVersion.value, - (pluginCrossBuild / sbtBinaryVersion).value, - sbtPlugin.value, - crossPaths.value - ) - val conf = configuration.value.toConfigRef - val avroArtifacts = update.value - .filter(avroDependencyIncludeFilter.value) - .toSeq - .collect { case (`conf`, _, _, file) => file } - - unpack( - cacheBaseDirectory = cacheBaseDirectory, - deps = avroArtifacts, - extractTarget = (key / target).value, - includeFilter = (key / includeFilter).value, - excludeFilter = (key / excludeFilter).value, - streams = (key / streams).value - ) - } - - def recompile( - records: Seq[Class[_ <: SpecificRecord]], - target: File, - log: Logger, - stringType: StringType, - fieldVisibility: FieldVisibility, - enableDecimalLogicalType: Boolean, - useNamespace: Boolean, - optionalGetters: Option[Boolean], - createSetters: Boolean, - builder: SchemaParserBuilder - ) = { - val compiler = new AvscFilesCompiler(builder) - compiler.setStringType(stringType) - compiler.setFieldVisibility(fieldVisibility) - compiler.setUseNamespace(useNamespace) - compiler.setEnableDecimalLogicalType(enableDecimalLogicalType) - compiler.setCreateSetters(createSetters) - optionalGetters.foreach(compiler.setOptionalGetters) - compiler.setLogCompileExceptions(true) - compiler.setTemplateDirectory("/org/apache/avro/compiler/specific/templates/java/classic/") - - records.foreach { avsc => - log.info(s"Compiling Avro schemas $avsc") - } - compiler.compileClasses(records.toSet.asJava, target) - } - - def compileIdls( - idls: Seq[File], - target: File, - log: Logger, - stringType: StringType, - fieldVisibility: FieldVisibility, - enableDecimalLogicalType: Boolean, - optionalGetters: Option[Boolean], - createSetters: Boolean - ) = { - idls.foreach { idl => - log.info(s"Compiling Avro IDL $idl") - val parser = new Idl(idl) - val protocol = Protocol.parse(parser.CompilationUnit.toString) - val compiler = new SpecificCompiler(protocol) - compiler.setStringType(stringType) - compiler.setFieldVisibility(fieldVisibility) - compiler.setEnableDecimalLogicalType(enableDecimalLogicalType) - compiler.setCreateSetters(createSetters) - optionalGetters.foreach(compiler.setGettersReturnOptional) - optionalGetters.foreach(compiler.setOptionalGettersForNullableFieldsOnly) - compiler.compileToDestination(null, target) - } - } - - def compileAvscs( - refs: Seq[AvroFileRef], - target: File, - log: Logger, - stringType: StringType, - fieldVisibility: FieldVisibility, - enableDecimalLogicalType: Boolean, - useNamespace: Boolean, - optionalGetters: Option[Boolean], - createSetters: Boolean, - builder: SchemaParserBuilder - ) = { - val compiler = new AvscFilesCompiler(builder) - compiler.setStringType(stringType) - compiler.setFieldVisibility(fieldVisibility) - compiler.setUseNamespace(useNamespace) - compiler.setEnableDecimalLogicalType(enableDecimalLogicalType) - compiler.setCreateSetters(createSetters) - optionalGetters.foreach(compiler.setOptionalGetters) - compiler.setLogCompileExceptions(true) - compiler.setTemplateDirectory("/org/apache/avro/compiler/specific/templates/java/classic/") - - refs.foreach { avsc => - log.info(s"Compiling Avro schemas $avsc") - } - compiler.compileFiles(refs.toSet.asJava, target) - } - - def compileAvprs( - avprs: Seq[File], - target: File, - log: Logger, - stringType: StringType, - fieldVisibility: FieldVisibility, - enableDecimalLogicalType: Boolean, - optionalGetters: Option[Boolean], - createSetters: Boolean - ) = { - avprs.foreach { avpr => - log.info(s"Compiling Avro protocol $avpr") - val protocol = Protocol.parse(avpr) - val compiler = new SpecificCompiler(protocol) - compiler.setStringType(stringType) - compiler.setFieldVisibility(fieldVisibility) - compiler.setEnableDecimalLogicalType(enableDecimalLogicalType) - compiler.setCreateSetters(createSetters) - optionalGetters.foreach(compiler.setGettersReturnOptional) - optionalGetters.foreach(compiler.setOptionalGettersForNullableFieldsOnly) - compiler.compileToDestination(null, target) - } - } - - private[this] def compileAvroSchema( - records: Seq[Class[_ <: SpecificRecord]], - srcDirs: Seq[File], - target: File, - log: Logger, - stringType: StringType, - fieldVisibility: FieldVisibility, - enableDecimalLogicalType: Boolean, - useNamespace: Boolean, - optionalGetters: Option[Boolean], - createSetters: Boolean, - builder: SchemaParserBuilder - ): Set[File] = { - val avdls = srcDirs.flatMap(d => (d ** AvroAvdlFilter).get) - val avscs = srcDirs.flatMap(d => - (d ** AvroAvscFilter).get.map(avsc => new AvroFileRef(d, avsc.relativeTo(d).get.toString)) - ) - val avprs = srcDirs.flatMap(d => (d ** AvroAvrpFilter).get) - - log.info(s"Avro compiler $avroCompilerVersion using stringType=$stringType") - recompile( - records, - target, - log, - stringType, - fieldVisibility, - enableDecimalLogicalType, - useNamespace, - optionalGetters, - createSetters, - builder - ) - compileIdls( - avdls, - target, - log, - stringType, - fieldVisibility, - enableDecimalLogicalType, - optionalGetters, - createSetters - ) - compileAvscs( - avscs, - target, - log, - stringType, - fieldVisibility, - enableDecimalLogicalType, - useNamespace, - optionalGetters, - createSetters, - builder - ) - compileAvprs( - avprs, - target, - log, - stringType, - fieldVisibility, - enableDecimalLogicalType, - optionalGetters, - createSetters - ) - - (target ** JavaFileFilter).get.toSet - } - - private def sourceGeneratorTask(key: TaskKey[Seq[File]]) = Def.task { - val out = (key / streams).value - - val srcDir = avroSource.value - val externalSrcDir = (avroUnpackDependencies / target).value - val includes = avroIncludes.value - val srcDirs = Seq(externalSrcDir, srcDir) ++ includes - val outDir = (key / target).value - val strType = StringType.valueOf(avroStringType.value) - val fieldVis = SpecificCompiler.FieldVisibility.valueOf(avroFieldVisibility.value.toUpperCase) - val enbDecimal = avroEnableDecimalLogicalType.value - val useNs = avroUseNamespace.value - val createSetters = avroCreateSetters.value - val optionalGetters = partialVersion(avroCompilerVersion) match { - case Some((1, minor)) if minor >= 10 => Some(avroOptionalGetters.value) - case _ => None - } - val builder = avroSchemaParserBuilder.value - val cachedCompile = { - import sbt.util.CacheStoreFactory - import sbt.util.CacheImplicits._ - - val cacheStoreFactory = CacheStoreFactory(out.cacheDirectory / "avro") - val lastCache = { (action: Option[Set[File]] => Set[File]) => - Tracked - .lastOutput[Unit, Set[File]](cacheStoreFactory.make("last-cache")) { case (_, l) => - action(l) - } - .apply(()) - } - val inCache = Difference.inputs(cacheStoreFactory.make("in-cache"), FileInfo.lastModified) - val outCache = Difference.outputs(cacheStoreFactory.make("out-cache"), FileInfo.exists) - - (inputs: Set[File], records: Seq[Class[_ <: SpecificRecord]]) => - lastCache { lastCache => - inCache(inputs) { inReport => - outCache { outReport => - if ( - (lastCache.isEmpty && records.nonEmpty) || inReport.modified.nonEmpty || outReport.modified.nonEmpty - ) { - // compile if - // - no previous cache and we have records to recompile - // - input files have changed - // - output files are missing - compileAvroSchema( - records, - srcDirs, - outDir, - out.log, - strType, - fieldVis, - enbDecimal, - useNs, - optionalGetters, - createSetters, - builder - ) - } else { - outReport.checked - } - } - } - } - } - - cachedCompile((srcDirs ** AvroFilter).get.toSet, avroSpecificRecords.value).toSeq - } - -} diff --git a/src/sbt-test/sbt-avro/avscparser/build.sbt b/src/sbt-test/sbt-avro/avscparser/build.sbt deleted file mode 100644 index 1ce1066..0000000 --- a/src/sbt-test/sbt-avro/avscparser/build.sbt +++ /dev/null @@ -1,14 +0,0 @@ -import java.util.Collections.{singletonMap, singletonList} -import org.apache.avro.Schema - -name := "avscparser-test" -scalaVersion := "2.13.11" -libraryDependencies ++= Seq( - "org.apache.avro" % "avro" % avroCompilerVersion, - "org.specs2" %% "specs2-core" % "4.20.9" % Test -) -avroSchemaParserBuilder := AnnotateWithArtifactSchemaParser - .newBuilder(projectID.value) - .copy( - types = singletonMap("B", Schema.createEnum("B", null, "com.github.sbt.avro.test", singletonList("B1")) - )) diff --git a/src/sbt-test/sbt-avro/avscparser/project/AnnotateWithArtifactSchemaParser.scala b/src/sbt-test/sbt-avro/avscparser/project/AnnotateWithArtifactSchemaParser.scala deleted file mode 100644 index 2e86f87..0000000 --- a/src/sbt-test/sbt-avro/avscparser/project/AnnotateWithArtifactSchemaParser.scala +++ /dev/null @@ -1,34 +0,0 @@ -import com.github.sbt.avro.mojo.SchemaParserBuilder -import org.apache.avro.Schema -import sbt.ModuleID - -class AnnotateWithArtifactSchemaParser( - moduleID: ModuleID, - types: java.util.Map[String, Schema] -) extends org.apache.avro.Schema.Parser { - - addTypes(types) - - override def parse(file: java.io.File): org.apache.avro.Schema = { - val schema = super.parse(file) - if (schema.getType == org.apache.avro.Schema.Type.RECORD) { - schema.addProp("com.github.sbt.sbt-avro.artifact", moduleID.toString()) - } - schema - } - -} - -object AnnotateWithArtifactSchemaParser { - - case class Builder(moduleID: ModuleID, types: java.util.Map[String, Schema]) - extends SchemaParserBuilder { - - override def build(): Schema.Parser = - new AnnotateWithArtifactSchemaParser(moduleID, types) - } - - def newBuilder(moduleID: ModuleID): AnnotateWithArtifactSchemaParser.Builder = - new Builder(moduleID, java.util.Collections.emptyMap()) - -} diff --git a/src/sbt-test/sbt-avro/avscparser/src/main/avro/a.avsc b/src/sbt-test/sbt-avro/avscparser/src/main/avro/a.avsc deleted file mode 100644 index 186ad92..0000000 --- a/src/sbt-test/sbt-avro/avscparser/src/main/avro/a.avsc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "A", - "namespace": "com.github.sbt.avro.test", - "type": "record", - "fields": [ - { - "name": "supportsCustomRegisteredType", - "type": "B" - } - ] -} diff --git a/src/sbt-test/sbt-avro/avscparser/src/test/scala/com/github/sbt/avro/test/AvscParserSpec.scala b/src/sbt-test/sbt-avro/avscparser/src/test/scala/com/github/sbt/avro/test/AvscParserSpec.scala deleted file mode 100644 index a82a5e1..0000000 --- a/src/sbt-test/sbt-avro/avscparser/src/test/scala/com/github/sbt/avro/test/AvscParserSpec.scala +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.sbt.avro.test - -import java.io.File - -import org.apache.avro.Schema -import org.apache.avro.generic.GenericData.StringType -import org.specs2.mutable.Specification - -import com.github.sbt.avro.test.A - -class AvscParserSpec extends Specification { - - "A should have artifact property" >> { - A.getClassSchema().getProp("com.github.sbt.sbt-avro.artifact") == "avscparser-test:avscparser-test:0.1.0-SNAPSHOT" - } - -} diff --git a/src/sbt-test/sbt-avro/avscparser/test b/src/sbt-test/sbt-avro/avscparser/test deleted file mode 100644 index 4efa91d..0000000 --- a/src/sbt-test/sbt-avro/avscparser/test +++ /dev/null @@ -1,10 +0,0 @@ -> compile - -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/A.java -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/B.java - -> test - -> clean - -> compile diff --git a/src/sbt-test/sbt-avro/basic_1.10/build.sbt b/src/sbt-test/sbt-avro/basic_1.10/build.sbt deleted file mode 100644 index e4eb83b..0000000 --- a/src/sbt-test/sbt-avro/basic_1.10/build.sbt +++ /dev/null @@ -1,3 +0,0 @@ -name := "basic-test" -scalaVersion := "2.13.11" -libraryDependencies += "org.apache.avro" % "avro" % avroCompilerVersion diff --git a/src/sbt-test/sbt-avro/basic_1.10/project/build.properties b/src/sbt-test/sbt-avro/basic_1.10/project/build.properties deleted file mode 120000 index 0adf10d..0000000 --- a/src/sbt-test/sbt-avro/basic_1.10/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -../../basic_current/project/build.properties \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.10/project/plugins.sbt b/src/sbt-test/sbt-avro/basic_1.10/project/plugins.sbt deleted file mode 100644 index bd2ad61..0000000 --- a/src/sbt-test/sbt-avro/basic_1.10/project/plugins.sbt +++ /dev/null @@ -1,6 +0,0 @@ -sys.props.get("plugin.version") match { - case Some(x) => addSbtPlugin("com.github.sbt" % "sbt-avro" % x) - case _ => sys.error("""|The system property 'plugin.version' is not defined. - |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) -} -libraryDependencies += "org.apache.avro" % "avro-compiler" % "1.10.2" // scala-steward:off diff --git a/src/sbt-test/sbt-avro/basic_1.10/src b/src/sbt-test/sbt-avro/basic_1.10/src deleted file mode 120000 index 2c57068..0000000 --- a/src/sbt-test/sbt-avro/basic_1.10/src +++ /dev/null @@ -1 +0,0 @@ -../basic_current/src \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.10/test b/src/sbt-test/sbt-avro/basic_1.10/test deleted file mode 120000 index 42415c7..0000000 --- a/src/sbt-test/sbt-avro/basic_1.10/test +++ /dev/null @@ -1 +0,0 @@ -../basic_1.11/test \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.11/build.sbt b/src/sbt-test/sbt-avro/basic_1.11/build.sbt deleted file mode 100644 index e4eb83b..0000000 --- a/src/sbt-test/sbt-avro/basic_1.11/build.sbt +++ /dev/null @@ -1,3 +0,0 @@ -name := "basic-test" -scalaVersion := "2.13.11" -libraryDependencies += "org.apache.avro" % "avro" % avroCompilerVersion diff --git a/src/sbt-test/sbt-avro/basic_1.11/project/build.properties b/src/sbt-test/sbt-avro/basic_1.11/project/build.properties deleted file mode 120000 index 0adf10d..0000000 --- a/src/sbt-test/sbt-avro/basic_1.11/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -../../basic_current/project/build.properties \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.11/project/plugins.sbt b/src/sbt-test/sbt-avro/basic_1.11/project/plugins.sbt deleted file mode 100644 index 1f19dd4..0000000 --- a/src/sbt-test/sbt-avro/basic_1.11/project/plugins.sbt +++ /dev/null @@ -1,6 +0,0 @@ -sys.props.get("plugin.version") match { - case Some(x) => addSbtPlugin("com.github.sbt" % "sbt-avro" % x) - case _ => sys.error("""|The system property 'plugin.version' is not defined. - |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) -} -libraryDependencies += "org.apache.avro" % "avro-compiler" % "1.11.3" // scala-steward:off diff --git a/src/sbt-test/sbt-avro/basic_1.11/src b/src/sbt-test/sbt-avro/basic_1.11/src deleted file mode 120000 index 2c57068..0000000 --- a/src/sbt-test/sbt-avro/basic_1.11/src +++ /dev/null @@ -1 +0,0 @@ -../basic_current/src \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.8/build.sbt b/src/sbt-test/sbt-avro/basic_1.8/build.sbt deleted file mode 100644 index 8f52435..0000000 --- a/src/sbt-test/sbt-avro/basic_1.8/build.sbt +++ /dev/null @@ -1,6 +0,0 @@ -name := "basic-test" -scalaVersion := "2.13.11" -libraryDependencies ++= Seq( - "org.apache.avro" % "avro" % avroCompilerVersion, - "joda-time" % "joda-time" % "2.7" // marked as optional in avro pom -) diff --git a/src/sbt-test/sbt-avro/basic_1.8/project/build.properties b/src/sbt-test/sbt-avro/basic_1.8/project/build.properties deleted file mode 120000 index 0adf10d..0000000 --- a/src/sbt-test/sbt-avro/basic_1.8/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -../../basic_current/project/build.properties \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.8/project/plugins.sbt b/src/sbt-test/sbt-avro/basic_1.8/project/plugins.sbt deleted file mode 100644 index cfe5603..0000000 --- a/src/sbt-test/sbt-avro/basic_1.8/project/plugins.sbt +++ /dev/null @@ -1,6 +0,0 @@ -sys.props.get("plugin.version") match { - case Some(x) => addSbtPlugin("com.github.sbt" % "sbt-avro" % x) - case _ => sys.error("""|The system property 'plugin.version' is not defined. - |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) -} -libraryDependencies += "org.apache.avro" % "avro-compiler" % "1.8.2" // scala-steward:off diff --git a/src/sbt-test/sbt-avro/basic_1.8/src b/src/sbt-test/sbt-avro/basic_1.8/src deleted file mode 120000 index 2c57068..0000000 --- a/src/sbt-test/sbt-avro/basic_1.8/src +++ /dev/null @@ -1 +0,0 @@ -../basic_current/src \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.8/test b/src/sbt-test/sbt-avro/basic_1.8/test deleted file mode 120000 index 42415c7..0000000 --- a/src/sbt-test/sbt-avro/basic_1.8/test +++ /dev/null @@ -1 +0,0 @@ -../basic_1.11/test \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.9/build.sbt b/src/sbt-test/sbt-avro/basic_1.9/build.sbt deleted file mode 100644 index 6c0b990..0000000 --- a/src/sbt-test/sbt-avro/basic_1.9/build.sbt +++ /dev/null @@ -1,6 +0,0 @@ -name := "basic-test" -scalaVersion := "2.13.11" -libraryDependencies ++= Seq( - "org.apache.avro" % "avro" % avroCompilerVersion, - "joda-time" % "joda-time" % "2.10.1" // marked as optional in avro pom -) diff --git a/src/sbt-test/sbt-avro/basic_1.9/project/build.properties b/src/sbt-test/sbt-avro/basic_1.9/project/build.properties deleted file mode 120000 index 0adf10d..0000000 --- a/src/sbt-test/sbt-avro/basic_1.9/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -../../basic_current/project/build.properties \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.9/project/plugins.sbt b/src/sbt-test/sbt-avro/basic_1.9/project/plugins.sbt deleted file mode 100644 index cb1f136..0000000 --- a/src/sbt-test/sbt-avro/basic_1.9/project/plugins.sbt +++ /dev/null @@ -1,6 +0,0 @@ -sys.props.get("plugin.version") match { - case Some(x) => addSbtPlugin("com.github.sbt" % "sbt-avro" % x) - case _ => sys.error("""|The system property 'plugin.version' is not defined. - |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) -} -libraryDependencies += "org.apache.avro" % "avro-compiler" % "1.9.2" // scala-steward:off diff --git a/src/sbt-test/sbt-avro/basic_1.9/src b/src/sbt-test/sbt-avro/basic_1.9/src deleted file mode 120000 index 2c57068..0000000 --- a/src/sbt-test/sbt-avro/basic_1.9/src +++ /dev/null @@ -1 +0,0 @@ -../basic_current/src \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_1.9/test b/src/sbt-test/sbt-avro/basic_1.9/test deleted file mode 120000 index 42415c7..0000000 --- a/src/sbt-test/sbt-avro/basic_1.9/test +++ /dev/null @@ -1 +0,0 @@ -../basic_1.11/test \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/basic_current/build.sbt b/src/sbt-test/sbt-avro/basic_current/build.sbt deleted file mode 100644 index e4eb83b..0000000 --- a/src/sbt-test/sbt-avro/basic_current/build.sbt +++ /dev/null @@ -1,3 +0,0 @@ -name := "basic-test" -scalaVersion := "2.13.11" -libraryDependencies += "org.apache.avro" % "avro" % avroCompilerVersion diff --git a/src/sbt-test/sbt-avro/basic_current/src/main/avro/f.avsc b/src/sbt-test/sbt-avro/basic_current/src/main/avro/f.avsc deleted file mode 100644 index 20b534c..0000000 --- a/src/sbt-test/sbt-avro/basic_current/src/main/avro/f.avsc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "record", - "name": "com.github.sbt.avro.test.F", - "doc": "Wrong default for field", - "fields": [ - { - "name": "f", - "type": "boolean", - "default": "invalid default value" - } - ] -} diff --git a/src/sbt-test/sbt-avro/basic_current/test b/src/sbt-test/sbt-avro/basic_current/test deleted file mode 100644 index c69d5ea..0000000 --- a/src/sbt-test/sbt-avro/basic_current/test +++ /dev/null @@ -1,42 +0,0 @@ -> set avroSchemaParserBuilder := com.github.sbt.avro.NameValidatorSchemaParserBuilder(validateDefaults = false) -> avroGenerate - -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/A.java -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/B.java -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/C.java -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/D.java -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/E.java -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/_A.java -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/_B.java -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/_C.java -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/_D.java -$ exists target/scala-2.13/src_managed/compiled_avro/main/com/github/sbt/avro/test/_E.java - -> compile - -$ exists target/scala-2.13/classes/com/github/sbt/avro/test/A.class -$ exists target/scala-2.13/classes/com/github/sbt/avro/test/B.class -$ exists target/scala-2.13/classes/com/github/sbt/avro/test/C.class -$ exists target/scala-2.13/classes/com/github/sbt/avro/test/D.class -$ exists target/scala-2.13/classes/com/github/sbt/avro/test/E.class -$ exists target/scala-2.13/classes/com/github/sbt/avro/test/_A.class -$ exists target/scala-2.13/classes/com/github/sbt/avro/test/_B.class -$ exists target/scala-2.13/classes/com/github/sbt/avro/test/_C.class -$ exists target/scala-2.13/classes/com/github/sbt/avro/test/_D.class -$ exists target/scala-2.13/classes/com/github/sbt/avro/test/_E.class - -> Test/compile - -$ exists target/scala-2.13/src_managed/compiled_avro/test/com/github/sbt/avro/test/X.java -$ exists target/scala-2.13/src_managed/compiled_avro/test/com/github/sbt/avro/test/Y.java -$ exists target/scala-2.13/src_managed/compiled_avro/test/com/github/sbt/avro/test/Z.java -$ exists target/scala-2.13/test-classes/com/github/sbt/avro/test/X.class -$ exists target/scala-2.13/test-classes/com/github/sbt/avro/test/Y.class -$ exists target/scala-2.13/test-classes/com/github/sbt/avro/test/Z.class - -> clean - -> set avroSchemaParserBuilder := com.github.sbt.avro.NameValidatorSchemaParserBuilder(validateDefaults = true) - -# should fail because f.avsc has invalid default value --> avroGenerate diff --git a/src/sbt-test/sbt-avro/recompile/project/plugins.sbt b/src/sbt-test/sbt-avro/recompile/project/plugins.sbt deleted file mode 100644 index 9fbf75b..0000000 --- a/src/sbt-test/sbt-avro/recompile/project/plugins.sbt +++ /dev/null @@ -1,13 +0,0 @@ -sys.props.get("plugin.version") match { - case Some(x) => - addSbtPlugin("com.github.sbt" % "sbt-avro" % x) - case _ => sys.error("""|The system property 'plugin.version' is not defined. - |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) -} - -val avroVersion = "1.12.0" -libraryDependencies ++= Seq( - "org.apache.avro" % "avro-compiler" % avroVersion, - // depend on test jar to get some generated records in the build - "org.apache.avro" % "avro" % avroVersion classifier "tests" -) diff --git a/src/sbt-test/sbt-avro/sbt_1.3/build.sbt b/src/sbt-test/sbt-avro/sbt_1.3/build.sbt deleted file mode 100644 index e4eb83b..0000000 --- a/src/sbt-test/sbt-avro/sbt_1.3/build.sbt +++ /dev/null @@ -1,3 +0,0 @@ -name := "basic-test" -scalaVersion := "2.13.11" -libraryDependencies += "org.apache.avro" % "avro" % avroCompilerVersion diff --git a/src/sbt-test/sbt-avro/sbt_1.3/src b/src/sbt-test/sbt-avro/sbt_1.3/src deleted file mode 120000 index 2c57068..0000000 --- a/src/sbt-test/sbt-avro/sbt_1.3/src +++ /dev/null @@ -1 +0,0 @@ -../basic_current/src \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/sbt_1.3/test b/src/sbt-test/sbt-avro/sbt_1.3/test deleted file mode 120000 index 088b752..0000000 --- a/src/sbt-test/sbt-avro/sbt_1.3/test +++ /dev/null @@ -1 +0,0 @@ -../basic_current/test \ No newline at end of file diff --git a/src/sbt-test/sbt-avro/settings/project/plugins.sbt b/src/sbt-test/sbt-avro/settings/project/plugins.sbt deleted file mode 100644 index 7f899a1..0000000 --- a/src/sbt-test/sbt-avro/settings/project/plugins.sbt +++ /dev/null @@ -1,12 +0,0 @@ -sys.props.get("plugin.version") match { - case Some(x) => addSbtPlugin("com.github.sbt" % "sbt-avro" % x) - case _ => sys.error("""|The system property 'plugin.version' is not defined. - |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) -} - -val avroVersion = "1.12.0" -libraryDependencies ++= Seq( - "org.apache.avro" % "avro-compiler" % avroVersion, - // depend on test jar to get some generated records in the build - "org.apache.avro" % "avro" % avroVersion classifier "tests" -)