From 1a5cc6b205d413aca6b1fd3dd16aacea386ed7a5 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Mon, 12 Nov 2018 11:54:14 +0100 Subject: [PATCH] Use plugin-generator-api in yang-maven-plugin Rework plugin execution so that we can use both old-style and new-style plugins by introducing GeneratorTask. This is then specialized for both worlds. We rework the multi-version support so that we run reactor assemply twice if needed -- for each requested mode separately. JIRA: YANGTOOLS-1147 Change-Id: I494ad899fabfb065c91537019698fdb131e0f1f2 Signed-off-by: Robert Varga --- .../generator/api/GeneratedFileType.java | 17 +- .../plugin/it/YangToSourcesPluginTestIT.java | 19 +- .../test-parent/FileGenerator/pom.xml | 66 ++++ .../test-parent/NoGenerators/pom.xml | 8 - .../src/test/resources/test-parent/pom.xml | 1 + .../yang2sources/spi/TestFileGenerator.java | 49 +++ .../spi/TestFileGeneratorFactory.java | 27 ++ ....plugin.generator.api.FileGeneratorFactory | 1 + .../plugin/CodeGeneratorTask.java | 64 ++++ .../plugin/CodeGeneratorTaskFactory.java | 118 +++++++ .../yang2sources/plugin/ConfigArg.java | 18 +- .../yang2sources/plugin/FileGeneratorArg.java | 54 +++ .../plugin/FileGeneratorTask.java | 198 +++++++++++ .../plugin/FileGeneratorTaskFactory.java | 56 ++++ .../yang2sources/plugin/GeneratorTask.java | 41 +++ .../plugin/GeneratorTaskFactory.java | 62 ++++ .../yang2sources/plugin/ParserModeAware.java | 17 + .../plugin/ProcessorModuleReactor.java | 2 +- .../plugin/YangToSourcesMojo.java | 30 +- .../plugin/YangToSourcesProcessor.java | 317 +++++++----------- .../plugin/YangToSourcesMojoTest.java | 29 +- .../plugin/YangToSourcesProcessorTest.java | 40 ++- 22 files changed, 974 insertions(+), 260 deletions(-) create mode 100644 plugin/yang-maven-plugin-it/src/test/resources/test-parent/FileGenerator/pom.xml create mode 100644 plugin/yang-maven-plugin-spi/src/test/java/org/opendaylight/yangtools/yang2sources/spi/TestFileGenerator.java create mode 100644 plugin/yang-maven-plugin-spi/src/test/java/org/opendaylight/yangtools/yang2sources/spi/TestFileGeneratorFactory.java create mode 100644 plugin/yang-maven-plugin-spi/src/test/resources/META-INF/services/org.opendaylight.yangtools.plugin.generator.api.FileGeneratorFactory create mode 100644 plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/CodeGeneratorTask.java create mode 100644 plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/CodeGeneratorTaskFactory.java create mode 100644 plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorArg.java create mode 100644 plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorTask.java create mode 100644 plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorTaskFactory.java create mode 100644 plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/GeneratorTask.java create mode 100644 plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/GeneratorTaskFactory.java create mode 100644 plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ParserModeAware.java diff --git a/plugin/plugin-generator-api/src/main/java/org/opendaylight/yangtools/plugin/generator/api/GeneratedFileType.java b/plugin/plugin-generator-api/src/main/java/org/opendaylight/yangtools/plugin/generator/api/GeneratedFileType.java index 0c53ac55c9..ef871484b0 100644 --- a/plugin/plugin-generator-api/src/main/java/org/opendaylight/yangtools/plugin/generator/api/GeneratedFileType.java +++ b/plugin/plugin-generator-api/src/main/java/org/opendaylight/yangtools/plugin/generator/api/GeneratedFileType.java @@ -17,8 +17,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; /** - * Type of generated file. Four most common kinds are captured in {@link #RESOURCE}, {@link #SOURCE}, - * {@link #TEST_RESOURCE} and {@link #TEST_SOURCE}, but others may be externally defined. + * Type of generated file. Two most common kinds are captured in {@link #RESOURCE}, {@link #SOURCE}, but others may be + * externally defined. * *

* Users of {@link FileGenerator} are expected to provide sensible mapping of {@link GeneratedFileType} to their @@ -41,19 +41,8 @@ public final class GeneratedFileType { */ public static final GeneratedFileType SOURCE = new GeneratedFileType("source"); - /** - * A generated test resource file. This file should be part of test resources. - */ - public static final GeneratedFileType TEST_RESOURCE = new GeneratedFileType("test-resource"); - - /** - * A generated test source file. This file should be part of test sources. - */ - public static final GeneratedFileType TEST_SOURCE = new GeneratedFileType("test-source"); - private static final ImmutableMap WELL_KNOWN = ImmutableMap.of( - RESOURCE.name(), RESOURCE, TEST_RESOURCE.name(), TEST_RESOURCE, - SOURCE.name(), SOURCE, TEST_SOURCE.name(), TEST_SOURCE); + RESOURCE.name(), RESOURCE, SOURCE.name(), SOURCE); private final String name; diff --git a/plugin/yang-maven-plugin-it/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/it/YangToSourcesPluginTestIT.java b/plugin/yang-maven-plugin-it/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/it/YangToSourcesPluginTestIT.java index d03addaf44..99ad946f5b 100644 --- a/plugin/yang-maven-plugin-it/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/it/YangToSourcesPluginTestIT.java +++ b/plugin/yang-maven-plugin-it/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/it/YangToSourcesPluginTestIT.java @@ -94,7 +94,8 @@ public class YangToSourcesPluginTestIT { Verifier vrf = setUp("test-parent/UnknownGenerator/", true); vrf.verifyTextInLog("[INFO] yang-to-sources: Code generator instantiated from " + "org.opendaylight.yangtools.yang2sources.spi.CodeGeneratorTestImpl"); - vrf.verifyTextInLog("Failed to instantiate code generator unknown"); + vrf.verifyTextInLog("on project unknown-generator: Failed to find code generator class unknown"); + vrf.verifyTextInLog("MojoFailureException: Failed to find code generator class unknown"); vrf.verifyTextInLog("java.lang.ClassNotFoundException: unknown"); } @@ -152,6 +153,22 @@ public class YangToSourcesPluginTestIT { v2.assertFileNotPresent(buildDir + "/classes/META-INF/yang/types3@2013-02-27.yang"); } + @Test + public void testFileGenerator() throws Exception { + Verifier v1 = setUp("test-parent/FileGenerator/", false); + v1.executeGoal("clean"); + v1.executeGoal("package"); + + String buildDir = getMavenBuildDirectory(v1); + + v1.assertFilePresent(buildDir + "/generated-sources/" + + "org.opendaylight.yangtools.yang2sources.spi.TestFileGenerator/fooGenSource.test"); + v1.assertFilePresent(buildDir + "/generated-resources/" + + "org.opendaylight.yangtools.yang2sources.spi.TestFileGenerator/foo-gen-resource"); + v1.assertFilePresent(buildDir + "/../src/main/java/fooSource.test"); + v1.assertFilePresent(buildDir + "/../src/main/resources/foo-resource"); + } + private static String getMavenBuildDirectory(final Verifier verifier) throws IOException { final Properties sp = new Properties(); final Path path = new File(verifier.getBasedir() + "/it-project.properties").toPath(); diff --git a/plugin/yang-maven-plugin-it/src/test/resources/test-parent/FileGenerator/pom.xml b/plugin/yang-maven-plugin-it/src/test/resources/test-parent/FileGenerator/pom.xml new file mode 100644 index 0000000000..56d89ce682 --- /dev/null +++ b/plugin/yang-maven-plugin-it/src/test/resources/test-parent/FileGenerator/pom.xml @@ -0,0 +1,66 @@ + + + + + 4.0.0 + + + org.opendaylight.yangtools + test-parent + @project.version@ + + + file-generator + + + ${project.build.directory} + + + + + + org.opendaylight.yangtools + yang-maven-plugin + ${project.version} + + + + generate-sources + + + ../files + false + + + org.opendaylight.yangtools.yang2sources.spi.TestFileGenerator + + foo + + + + + + + + + + org.opendaylight.yangtools + yang-maven-plugin-spi + ${project.version} + test-jar + + + + + org.codehaus.mojo + properties-maven-plugin + + + + diff --git a/plugin/yang-maven-plugin-it/src/test/resources/test-parent/NoGenerators/pom.xml b/plugin/yang-maven-plugin-it/src/test/resources/test-parent/NoGenerators/pom.xml index e5340fe467..759824896e 100644 --- a/plugin/yang-maven-plugin-it/src/test/resources/test-parent/NoGenerators/pom.xml +++ b/plugin/yang-maven-plugin-it/src/test/resources/test-parent/NoGenerators/pom.xml @@ -44,14 +44,6 @@ - - - org.opendaylight.yangtools - yang-maven-plugin-spi - ${project.version} - test-jar - - diff --git a/plugin/yang-maven-plugin-it/src/test/resources/test-parent/pom.xml b/plugin/yang-maven-plugin-it/src/test/resources/test-parent/pom.xml index 0924704822..114d8e79fc 100644 --- a/plugin/yang-maven-plugin-it/src/test/resources/test-parent/pom.xml +++ b/plugin/yang-maven-plugin-it/src/test/resources/test-parent/pom.xml @@ -15,6 +15,7 @@ additional-config correct + file-generator generate-test1 generate-test2 generator diff --git a/plugin/yang-maven-plugin-spi/src/test/java/org/opendaylight/yangtools/yang2sources/spi/TestFileGenerator.java b/plugin/yang-maven-plugin-spi/src/test/java/org/opendaylight/yangtools/yang2sources/spi/TestFileGenerator.java new file mode 100644 index 0000000000..a8a33a78c9 --- /dev/null +++ b/plugin/yang-maven-plugin-spi/src/test/java/org/opendaylight/yangtools/yang2sources/spi/TestFileGenerator.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang2sources.spi; + +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Table; +import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.yangtools.plugin.generator.api.FileGenerator; +import org.opendaylight.yangtools.plugin.generator.api.GeneratedFile; +import org.opendaylight.yangtools.plugin.generator.api.GeneratedFileLifecycle; +import org.opendaylight.yangtools.plugin.generator.api.GeneratedFilePath; +import org.opendaylight.yangtools.plugin.generator.api.GeneratedFileType; +import org.opendaylight.yangtools.plugin.generator.api.ModuleResourceResolver; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.Module; + +@NonNullByDefault +final class TestFileGenerator implements FileGenerator { + private final String prefix; + + TestFileGenerator(final String prefix) { + this.prefix = prefix; + } + + @Override + public Table generateFiles(final EffectiveModelContext context, + final Set localModules, final ModuleResourceResolver moduleResourcePathResolver) { + if (prefix == null) { + return ImmutableTable.of(); + } + + return ImmutableTable.builder() + .put(GeneratedFileType.SOURCE, GeneratedFilePath.ofFilePath(prefix + "Source.test"), + GeneratedFile.of(GeneratedFileLifecycle.PERSISTENT, "source")) + .put(GeneratedFileType.RESOURCE, GeneratedFilePath.ofFilePath(prefix + "-resource"), + GeneratedFile.of(GeneratedFileLifecycle.PERSISTENT, "test-resource")) + .put(GeneratedFileType.SOURCE, GeneratedFilePath.ofFilePath(prefix + "GenSource.test"), + GeneratedFile.of(GeneratedFileLifecycle.TRANSIENT, "source")) + .put(GeneratedFileType.RESOURCE, GeneratedFilePath.ofFilePath(prefix + "-gen-resource"), + GeneratedFile.of(GeneratedFileLifecycle.TRANSIENT, "resource")) + .build(); + } +} diff --git a/plugin/yang-maven-plugin-spi/src/test/java/org/opendaylight/yangtools/yang2sources/spi/TestFileGeneratorFactory.java b/plugin/yang-maven-plugin-spi/src/test/java/org/opendaylight/yangtools/yang2sources/spi/TestFileGeneratorFactory.java new file mode 100644 index 0000000000..a8a5b9d72c --- /dev/null +++ b/plugin/yang-maven-plugin-spi/src/test/java/org/opendaylight/yangtools/yang2sources/spi/TestFileGeneratorFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang2sources.spi; + +import java.util.Map; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.yangtools.plugin.generator.api.AbstractFileGeneratorFactory; +import org.opendaylight.yangtools.plugin.generator.api.FileGenerator; + +@NonNullByDefault +public final class TestFileGeneratorFactory extends AbstractFileGeneratorFactory { + public static final String PREFIX = "prefix"; + + public TestFileGeneratorFactory() { + super(TestFileGenerator.class.getName()); + } + + @Override + public FileGenerator newFileGenerator(final Map configuration) { + return new TestFileGenerator(configuration.get(PREFIX)); + } +} diff --git a/plugin/yang-maven-plugin-spi/src/test/resources/META-INF/services/org.opendaylight.yangtools.plugin.generator.api.FileGeneratorFactory b/plugin/yang-maven-plugin-spi/src/test/resources/META-INF/services/org.opendaylight.yangtools.plugin.generator.api.FileGeneratorFactory new file mode 100644 index 0000000000..b3658f40c2 --- /dev/null +++ b/plugin/yang-maven-plugin-spi/src/test/resources/META-INF/services/org.opendaylight.yangtools.plugin.generator.api.FileGeneratorFactory @@ -0,0 +1 @@ +org.opendaylight.yangtools.yang2sources.spi.TestFileGeneratorFactory diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/CodeGeneratorTask.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/CodeGeneratorTask.java new file mode 100644 index 0000000000..31409aafd9 --- /dev/null +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/CodeGeneratorTask.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang2sources.plugin; + +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Stopwatch; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.yangtools.yang2sources.spi.BasicCodeGenerator; +import org.opendaylight.yangtools.yang2sources.spi.BuildContextAware; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonatype.plexus.build.incremental.BuildContext; + +@NonNullByDefault +final class CodeGeneratorTask extends GeneratorTask { + private static final Logger LOG = LoggerFactory.getLogger(CodeGeneratorTask.class); + + private final File outputDir; + + CodeGeneratorTask(final CodeGeneratorTaskFactory factory, final ContextHolder context, final File outputDir) { + super(factory, context); + this.outputDir = requireNonNull(outputDir); + } + + @Override + Collection execute(final CodeGeneratorTaskFactory factory, final ContextHolder modelContext, + final BuildContext buildContext) throws IOException { + final Stopwatch watch = Stopwatch.createStarted(); + final BasicCodeGenerator gen = factory.generator(); + final boolean mark; + if (gen instanceof BuildContextAware) { + ((BuildContextAware)gen).setBuildContext(buildContext); + mark = false; + } else { + mark = true; + } + + LOG.debug("Sources will be generated to {}", outputDir); + Collection sources = gen.generateSources(modelContext.getContext(), outputDir, + modelContext.getYangModules(), modelContext); + if (sources == null) { + sources = List.of(); + } + + LOG.debug("Sources generated by {}: {}", gen.getClass(), sources); + LOG.info("Sources generated by {}: {} in {}", gen.getClass(), sources.size(), watch); + + if (mark) { + sources.forEach(buildContext::refresh); + } + + return sources; + } +} diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/CodeGeneratorTaskFactory.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/CodeGeneratorTaskFactory.java new file mode 100644 index 0000000000..8ac38cdbf7 --- /dev/null +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/CodeGeneratorTaskFactory.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang2sources.plugin; + +import static java.util.Objects.requireNonNull; + +import com.google.common.base.MoreObjects.ToStringHelper; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import org.apache.maven.model.Resource; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.project.MavenProject; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.yang2sources.plugin.ConfigArg.CodeGeneratorArg; +import org.opendaylight.yangtools.yang2sources.spi.BasicCodeGenerator; +import org.opendaylight.yangtools.yang2sources.spi.BasicCodeGenerator.ImportResolutionMode; +import org.opendaylight.yangtools.yang2sources.spi.MavenProjectAware; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Bridge to legacy {@link BasicCodeGenerator} generation. + * + * @author Robert Varga + */ +final class CodeGeneratorTaskFactory extends GeneratorTaskFactory { + private static final Logger LOG = LoggerFactory.getLogger(CodeGeneratorTaskFactory.class); + + private final @NonNull BasicCodeGenerator gen; + private final CodeGeneratorArg cfg; + + private CodeGeneratorTaskFactory(final ImportResolutionMode importMode, final BasicCodeGenerator gen, + final CodeGeneratorArg cfg) { + super(importMode.toFileGeneratorMode()); + this.gen = requireNonNull(gen); + this.cfg = requireNonNull(cfg); + } + + static GeneratorTaskFactory create(final CodeGeneratorArg cfg) throws MojoFailureException { + cfg.check(); + + final String codegenClass = cfg.getCodeGeneratorClass(); + final Class clazz; + try { + clazz = Class.forName(codegenClass); + } catch (ClassNotFoundException e) { + throw new MojoFailureException("Failed to find code generator class " + codegenClass, e); + } + final Class typedClass; + try { + typedClass = clazz.asSubclass(BasicCodeGenerator.class); + } catch (ClassCastException e) { + throw new MojoFailureException("Code generator " + clazz + " does not implement " + + BasicCodeGenerator.class, e); + } + final Constructor ctor; + try { + ctor = typedClass.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new MojoFailureException("Code generator " + clazz + + " does not have an accessible no-argument constructor", e); + } + final @NonNull BasicCodeGenerator gen; + try { + gen = ctor.newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new MojoFailureException("Failed to instantiate code generator " + clazz, e); + } + LOG.debug("Code generator instantiated from {}", codegenClass); + + ImportResolutionMode importMode = gen.getImportResolutionMode(); + if (importMode == null) { + importMode = ImportResolutionMode.REVISION_EXACT_OR_LATEST; + } + return new CodeGeneratorTaskFactory(importMode, gen, cfg); + } + + @Override + CodeGeneratorTask createTask(final MavenProject project, final ContextHolder context) { + if (gen instanceof MavenProjectAware) { + ((MavenProjectAware)gen).setMavenProject(project); + } + + LOG.debug("Project root dir is {}", project.getBasedir()); + LOG.debug("{} Additional configuration picked up for : {}: {}", YangToSourcesProcessor.LOG_PREFIX, + generatorName(), cfg.getAdditionalConfiguration()); + + final File outputDir = cfg.getOutputBaseDir(project); + project.addCompileSourceRoot(outputDir.getAbsolutePath()); + + gen.setAdditionalConfig(cfg.getAdditionalConfiguration()); + File resourceBaseDir = cfg.getResourceBaseDir(project); + + final Resource res = new Resource(); + res.setDirectory(resourceBaseDir.getPath()); + project.addResource(res); + + gen.setResourceBaseDir(resourceBaseDir); + LOG.debug("Folder: {} marked as resources for generator: {}", resourceBaseDir, generatorName()); + return new CodeGeneratorTask(this, context, outputDir); + } + + @Override + BasicCodeGenerator generator() { + return gen; + } + + @Override + ToStringHelper addToStringProperties(final ToStringHelper helper) { + return super.addToStringProperties(helper).add("configuration", cfg); + } +} diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ConfigArg.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ConfigArg.java index 5e6bb1157d..d653cad8c4 100644 --- a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ConfigArg.java +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ConfigArg.java @@ -9,6 +9,7 @@ package org.opendaylight.yangtools.yang2sources.plugin; import static java.util.Objects.requireNonNull; +import com.google.common.base.MoreObjects; import java.io.File; import java.util.HashMap; import java.util.Map; @@ -25,13 +26,13 @@ public abstract class ConfigArg { } public File getOutputBaseDir(final MavenProject project) { - if (outputBaseDir == null) { - return null; - } return outputBaseDir.isAbsolute() ? outputBaseDir : new File(project.getBasedir(), outputBaseDir.getPath()); } - public abstract void check(); + public void check() { + requireNonNull(outputBaseDir, + "outputBaseDir is null. Please provide a valid outputBaseDir value in the pom.xml"); + } /** * Configuration argument for code generator class and output directory. @@ -65,6 +66,7 @@ public abstract class ConfigArg { @Override public void check() { + super.check(); requireNonNull(codeGeneratorClass, "codeGeneratorClass for CodeGenerator cannot be null"); } @@ -85,5 +87,13 @@ public abstract class ConfigArg { public Map getAdditionalConfiguration() { return additionalConfiguration; } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).omitNullValues() + .add("resourceDir", resourceBaseDir) + .add("configuration", additionalConfiguration) + .toString(); + } } } diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorArg.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorArg.java new file mode 100644 index 0000000000..adf4cad35b --- /dev/null +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorArg.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang2sources.plugin; + +import static com.google.common.base.Verify.verifyNotNull; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.MoreObjects; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.apache.maven.plugins.annotations.Parameter; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.concepts.Identifiable; + +public final class FileGeneratorArg implements Identifiable { + @Parameter + private final Map configuration = new HashMap<>(); + + @Parameter(required = true) + private String identifier; + + public FileGeneratorArg() { + + } + + public FileGeneratorArg(final String identifier) { + this.identifier = requireNonNull(identifier); + } + + public FileGeneratorArg(final String identifier, final Map configuration) { + this(identifier); + this.configuration.putAll(configuration); + } + + @Override + public String getIdentifier() { + return verifyNotNull(identifier); + } + + public @NonNull Map getConfiguration() { + return Collections.unmodifiableMap(configuration); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("id", identifier).add("configuration", configuration).toString(); + } +} diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorTask.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorTask.java new file mode 100644 index 0000000000..1cd80f41c4 --- /dev/null +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorTask.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang2sources.plugin; + +import static com.google.common.base.Verify.verify; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Table; +import com.google.common.collect.Table.Cell; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.maven.model.Build; +import org.apache.maven.model.Resource; +import org.apache.maven.project.MavenProject; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.plugin.generator.api.FileGenerator; +import org.opendaylight.yangtools.plugin.generator.api.FileGeneratorException; +import org.opendaylight.yangtools.plugin.generator.api.GeneratedFile; +import org.opendaylight.yangtools.plugin.generator.api.GeneratedFilePath; +import org.opendaylight.yangtools.plugin.generator.api.GeneratedFileType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonatype.plexus.build.incremental.BuildContext; + +final class FileGeneratorTask extends GeneratorTask { + private static final Logger LOG = LoggerFactory.getLogger(FileGeneratorTask.class); + + private final Map persistentDirs = new HashMap<>(4); + private final Map transientDirs = new HashMap<>(4); + private final MavenProject project; + private final File buildDir; + private final String suffix; + + FileGeneratorTask(final @NonNull FileGeneratorTaskFactory factory, final @NonNull ContextHolder context, + final MavenProject project) { + super(factory, context); + + final Build build = project.getBuild(); + final String buildDirectory = build.getDirectory(); + this.buildDir = new File(buildDirectory); + this.suffix = factory.getIdentifier(); + this.project = project; + } + + @Override + Collection execute(final FileGeneratorTaskFactory factory, final ContextHolder modelContext, + final BuildContext buildContext) throws FileGeneratorException, IOException { + // Step one: determine what files are going to be generated + final Stopwatch sw = Stopwatch.createStarted(); + final FileGenerator gen = factory.generator(); + final Table generatedFiles = gen.generateFiles( + modelContext.getContext(), modelContext.getYangModules(), modelContext); + LOG.info("{}: Defined {} files in {}", suffix, generatedFiles.size(), sw); + + // Step two: create generation tasks for each target file and group them by parent directory + sw.reset().start(); + final ListMultimap dirs = MultimapBuilder.hashKeys().arrayListValues().build(); + for (Cell cell : generatedFiles.cellSet()) { + final GeneratedFile file = cell.getValue(); + final String relativePath = cell.getColumnKey().getPath(); + final File target; + switch (file.getLifecycle()) { + case PERSISTENT: + target = new File(persistentPath(cell.getRowKey()), relativePath); + if (target.exists()) { + LOG.debug("Skipping existing persistent {}", target); + continue; + } + break; + case TRANSIENT: + target = new File(transientPath(cell.getRowKey()), relativePath); + break; + default: + throw new IllegalStateException("Unsupported file type in " + file); + } + + dirs.put(target.getParentFile(), new WriteTask(buildContext, target, cell.getValue())); + } + LOG.info("Sorted {} files into {} directories in {}", dirs.size(), dirs.keySet().size(), sw); + + // Step three: submit parent directory creation tasks (via parallelStream()) and wait for them to complete + sw.reset().start(); + dirs.keySet().parallelStream().forEach(path -> { + try { + Files.createDirectories(path.toPath()); + } catch (IOException e) { + throw new IllegalStateException("Failed to create " + path, e); + } + }); + LOG.debug("Parent directories created in {}", sw); + + // Step four: submit all code generation tasks (via parallelStream()) and wait for them to complete + sw.reset().start(); + final List result = dirs.values().parallelStream() + .map(WriteTask::generateFile) + .collect(Collectors.toList()); + LOG.debug("Generated {} files in {}", result.size(), sw); + + return result; + } + + private File persistentPath(final GeneratedFileType fileType) throws FileGeneratorException { + final File existing = persistentDirs.get(fileType); + if (existing != null) { + return existing; + } + final File newDir = persistentDirectory(fileType); + verify(persistentDirs.put(fileType, newDir) == null); + return newDir; + } + + private File transientPath(final GeneratedFileType fileType) throws FileGeneratorException { + final File existing = transientDirs.get(fileType); + if (existing != null) { + return existing; + } + + final File newDir = transientDirectory(fileType); + verify(transientDirs.put(fileType, newDir) == null); + return newDir; + } + + private File persistentDirectory(final GeneratedFileType fileType) throws FileGeneratorException { + final File ret; + if (GeneratedFileType.SOURCE.equals(fileType)) { + ret = new File(project.getBuild().getSourceDirectory()); + } else if (GeneratedFileType.RESOURCE.equals(fileType)) { + ret = new File(new File(project.getBuild().getSourceDirectory()).getParentFile(), "resources"); + } else { + throw new FileGeneratorException("Unknown generated file type " + fileType); + } + return ret; + } + + private File transientDirectory(final GeneratedFileType fileType) throws FileGeneratorException { + final File ret; + if (GeneratedFileType.SOURCE.equals(fileType)) { + ret = transientDirectory("generated-sources"); + project.addCompileSourceRoot(ret.toString()); + } else if (GeneratedFileType.RESOURCE.equals(fileType)) { + ret = transientDirectory("generated-resources"); + project.addResource(createResouce(ret)); + } else { + throw new FileGeneratorException("Unknown generated file type " + fileType); + } + return ret; + } + + private File transientDirectory(final String component) { + return new File(buildDir, subdirFileName(component)); + } + + private String subdirFileName(final String component) { + return component + File.separatorChar + suffix; + } + + private static Resource createResouce(final File directory) { + final Resource ret = new Resource(); + ret.setDirectory(directory.toString()); + return ret; + } + + private static final class WriteTask { + private final BuildContext buildContext; + private final GeneratedFile file; + private final File target; + + WriteTask(final BuildContext buildContext, final File target, final GeneratedFile file) { + this.buildContext = requireNonNull(buildContext); + this.target = requireNonNull(target); + this.file = requireNonNull(file); + } + + File generateFile() { + try (OutputStream stream = buildContext.newFileOutputStream(target)) { + file.writeBody(stream); + } catch (IOException e) { + throw new IllegalStateException("Failed to generate file " + target, e); + } + return target; + } + } +} diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorTaskFactory.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorTaskFactory.java new file mode 100644 index 0000000000..f38a96e504 --- /dev/null +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/FileGeneratorTaskFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang2sources.plugin; + +import com.google.common.base.MoreObjects.ToStringHelper; +import org.apache.maven.project.MavenProject; +import org.opendaylight.yangtools.concepts.Identifiable; +import org.opendaylight.yangtools.plugin.generator.api.FileGenerator; +import org.opendaylight.yangtools.plugin.generator.api.FileGeneratorException; +import org.opendaylight.yangtools.plugin.generator.api.FileGeneratorFactory; + +/** + * Bridge to a {@link FileGenerator} instance. + * + * @author Robert Varga + */ +final class FileGeneratorTaskFactory extends GeneratorTaskFactory implements Identifiable { + private final FileGeneratorArg arg; + private final FileGenerator gen; + + private FileGeneratorTaskFactory(final FileGenerator gen, final FileGeneratorArg arg) { + super(gen.importResolutionMode()); + this.arg = arg; + this.gen = gen; + } + + static FileGeneratorTaskFactory of(final FileGeneratorFactory factory, final FileGeneratorArg arg) + throws FileGeneratorException { + return new FileGeneratorTaskFactory(factory.newFileGenerator(arg.getConfiguration()), arg); + } + + @Override + public String getIdentifier() { + return arg.getIdentifier(); + } + + @Override + FileGeneratorTask createTask(final MavenProject project, final ContextHolder context) { + return new FileGeneratorTask(this, context, project); + } + + @Override + FileGenerator generator() { + return gen; + } + + @Override + ToStringHelper addToStringProperties(final ToStringHelper helper) { + return super.addToStringProperties(helper).add("argument", arg); + } +} diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/GeneratorTask.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/GeneratorTask.java new file mode 100644 index 0000000000..52042ef018 --- /dev/null +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/GeneratorTask.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang2sources.plugin; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.yangtools.plugin.generator.api.FileGeneratorException; +import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode; +import org.sonatype.plexus.build.incremental.BuildContext; + +@NonNullByDefault +abstract class GeneratorTask extends ParserModeAware { + private final ContextHolder context; + private final T factory; + + GeneratorTask(final T factory, final ContextHolder context) { + this.factory = requireNonNull(factory); + this.context = requireNonNull(context); + } + + @Override + final StatementParserMode parserMode() { + return factory.parserMode(); + } + + final Collection execute(final BuildContext buildContext) throws FileGeneratorException, IOException { + return execute(factory, context, buildContext); + } + + abstract Collection execute(T factory, ContextHolder modelContext, BuildContext buildContext) + throws FileGeneratorException, IOException; +} diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/GeneratorTaskFactory.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/GeneratorTaskFactory.java new file mode 100644 index 0000000000..ea3df1b5d5 --- /dev/null +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/GeneratorTaskFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang2sources.plugin; + +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; +import org.apache.maven.project.MavenProject; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.yangtools.plugin.generator.api.FileGenerator.ImportResolutionMode; +import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode; + +@NonNullByDefault +abstract class GeneratorTaskFactory extends ParserModeAware { + private final StatementParserMode parserMode; + + GeneratorTaskFactory(final ImportResolutionMode importMode) { + switch (importMode) { + case REVISION_EXACT_OR_LATEST: + parserMode = StatementParserMode.DEFAULT_MODE; + break; + case SEMVER_LATEST: + parserMode = StatementParserMode.SEMVER_MODE; + break; + default: + throw new LinkageError("Unhandled import mode " + importMode); + } + } + + @Override + final StatementParserMode parserMode() { + return parserMode; + } + + final String generatorName() { + return generator().getClass().getName(); + } + + /** + * Create a new {@link GeneratorTask} which will work in scope of specified {@link MavenProject} with the effective + * model held in specified {@link ContextHolder}. + * + * @param project current Maven Project + * @param context model generation context + */ + abstract GeneratorTask createTask(MavenProject project, ContextHolder context); + + abstract Object generator(); + + ToStringHelper addToStringProperties(final ToStringHelper helper) { + return helper.add("generator", generatorName()); + } + + @Override + public final String toString() { + return addToStringProperties(MoreObjects.toStringHelper(this).omitNullValues()).toString(); + } +} diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ParserModeAware.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ParserModeAware.java new file mode 100644 index 0000000000..e76af00755 --- /dev/null +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ParserModeAware.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang2sources.plugin; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode; + +@NonNullByDefault +abstract class ParserModeAware { + + abstract StatementParserMode parserMode(); +} diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ProcessorModuleReactor.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ProcessorModuleReactor.java index fa1a2353e6..853eb850e9 100644 --- a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ProcessorModuleReactor.java +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/ProcessorModuleReactor.java @@ -48,7 +48,7 @@ final class ProcessorModuleReactor { private YangParser parser; ProcessorModuleReactor(final YangParser parser, final Collection modelsInProject, - final Collection dependencies) { + final Collection dependencies) { this.parser = requireNonNull(parser); this.modelsInProject = Maps.uniqueIndex(modelsInProject, YangTextSchemaSource::getIdentifier); this.dependencies = ImmutableList.copyOf(dependencies); diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesMojo.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesMojo.java index 0974a773e3..17ca037022 100644 --- a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesMojo.java +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesMojo.java @@ -14,7 +14,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Set; import org.apache.maven.artifact.repository.ArtifactRepository; @@ -28,7 +27,9 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.repository.RepositorySystem; +import org.opendaylight.yangtools.plugin.generator.api.FileGenerator; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode; import org.opendaylight.yangtools.yang2sources.plugin.ConfigArg.CodeGeneratorArg; import org.opendaylight.yangtools.yang2sources.spi.BasicCodeGenerator; import org.sonatype.plexus.build.incremental.BuildContext; @@ -61,6 +62,13 @@ public final class YangToSourcesMojo extends AbstractMojo { @Parameter(required = false) private CodeGeneratorArg[] codeGenerators; + /** + * {@link FileGenerator} instances resolved via ServiceLoader can hold additional configuration, which details + * how they are executed. + */ + @Parameter(required = false) + private FileGeneratorArg[] fileGenerators; + /** * Source directory that will be recursively searched for yang files (ending * with .yang suffix). @@ -97,6 +105,10 @@ public final class YangToSourcesMojo extends AbstractMojo { @Parameter(property = "yang.skip") private String yangSkip; + @Parameter(defaultValue = "DEFAULT_MODE") + @Deprecated(forRemoval = true) + private StatementParserMode parserMode; + public YangToSourcesMojo() { } @@ -116,26 +128,19 @@ public final class YangToSourcesMojo extends AbstractMojo { Util.checkClasspath(project, repoSystem, localRepository, remoteRepos); if (yangToSourcesProcessor == null) { - List codeGeneratorArgs = processCodeGenerators(codeGenerators); - // defaults to ${basedir}/src/main/yang File yangFilesRootFile = processYangFilesRootDir(yangFilesRootDir, project.getBasedir()); Collection excludedFiles = processExcludeFiles(excludeFiles, yangFilesRootFile); yangToSourcesProcessor = new YangToSourcesProcessor(buildContext, yangFilesRootFile, - excludedFiles, codeGeneratorArgs, project, inspectDependencies); + excludedFiles, arrayToList(codeGenerators), arrayToList(fileGenerators), project, + inspectDependencies); } yangToSourcesProcessor.conditionalExecute("true".equals(yangSkip)); } - private static List processCodeGenerators(final CodeGeneratorArg[] codeGenerators) { - List codeGeneratorArgs; - if (codeGenerators == null) { - codeGeneratorArgs = Collections.emptyList(); - } else { - codeGeneratorArgs = Arrays.asList(codeGenerators); - } - return codeGeneratorArgs; + private static List arrayToList(final T[] array) { + return array == null ? ImmutableList.of() : Arrays.asList(array); } private static File processYangFilesRootDir(final String yangFilesRootDir, final File baseDir) { @@ -160,5 +165,4 @@ public final class YangToSourcesMojo extends AbstractMojo { return Collections2.transform(Arrays.asList(excludeFiles), f -> new File(baseDir, f)); } - } diff --git a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesProcessor.java b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesProcessor.java index 9cf4412984..509e0f0a4c 100644 --- a/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesProcessor.java +++ b/plugin/yang-maven-plugin/src/main/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesProcessor.java @@ -7,7 +7,6 @@ */ package org.opendaylight.yangtools.yang2sources.plugin; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; @@ -16,15 +15,14 @@ import com.google.common.base.Stopwatch; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import com.google.common.collect.Maps; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -36,6 +34,8 @@ import java.util.stream.Collectors; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; +import org.opendaylight.yangtools.plugin.generator.api.FileGeneratorException; +import org.opendaylight.yangtools.plugin.generator.api.FileGeneratorFactory; import org.opendaylight.yangtools.yang.common.YangConstants; import org.opendaylight.yangtools.yang.model.parser.api.YangParser; import org.opendaylight.yangtools.yang.model.parser.api.YangParserException; @@ -46,10 +46,6 @@ import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRSchemaSource; import org.opendaylight.yangtools.yang.parser.rfc7950.repo.TextToIRTransformer; import org.opendaylight.yangtools.yang2sources.plugin.ConfigArg.CodeGeneratorArg; -import org.opendaylight.yangtools.yang2sources.spi.BasicCodeGenerator; -import org.opendaylight.yangtools.yang2sources.spi.BasicCodeGenerator.ImportResolutionMode; -import org.opendaylight.yangtools.yang2sources.spi.BuildContextAware; -import org.opendaylight.yangtools.yang2sources.spi.MavenProjectAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonatype.plexus.build.incremental.BuildContext; @@ -77,18 +73,21 @@ class YangToSourcesProcessor { private final File yangFilesRootDir; private final Set excludedFiles; private final List codeGeneratorArgs; + private final Map fileGeneratorArgs; private final MavenProject project; private final boolean inspectDependencies; private final BuildContext buildContext; private final YangProvider yangProvider; private YangToSourcesProcessor(final BuildContext buildContext, final File yangFilesRootDir, - final Collection excludedFiles, final List codeGenerators, + final Collection excludedFiles, final List codeGeneratorArgs, + final List fileGeneratorsArgs, final MavenProject project, final boolean inspectDependencies, final YangProvider yangProvider) { this.buildContext = requireNonNull(buildContext, "buildContext"); this.yangFilesRootDir = requireNonNull(yangFilesRootDir, "yangFilesRootDir"); this.excludedFiles = ImmutableSet.copyOf(excludedFiles); - this.codeGeneratorArgs = ImmutableList.copyOf(codeGenerators); + this.codeGeneratorArgs = ImmutableList.copyOf(codeGeneratorArgs); + this.fileGeneratorArgs = Maps.uniqueIndex(fileGeneratorsArgs, FileGeneratorArg::getIdentifier); this.project = requireNonNull(project); this.inspectDependencies = inspectDependencies; this.yangProvider = requireNonNull(yangProvider); @@ -99,15 +98,16 @@ class YangToSourcesProcessor { YangToSourcesProcessor(final File yangFilesRootDir, final Collection excludedFiles, final List codeGenerators, final MavenProject project, final boolean inspectDependencies, final YangProvider yangProvider) { - this(new DefaultBuildContext(), yangFilesRootDir, excludedFiles, codeGenerators, project, - inspectDependencies, yangProvider); + this(new DefaultBuildContext(), yangFilesRootDir, excludedFiles, codeGenerators, ImmutableList.of(), + project, inspectDependencies, yangProvider); } YangToSourcesProcessor(final BuildContext buildContext, final File yangFilesRootDir, final Collection excludedFiles, final List codeGenerators, - final MavenProject project, final boolean inspectDependencies) { - this(buildContext, yangFilesRootDir, excludedFiles, codeGenerators, project, inspectDependencies, - YangProvider.getInstance()); + final List fileGenerators, final MavenProject project, + final boolean inspectDependencies) { + this(buildContext, yangFilesRootDir, excludedFiles, codeGenerators, fileGenerators, project, + inspectDependencies, YangProvider.getInstance()); } public void execute() throws MojoExecutionException, MojoFailureException { @@ -134,38 +134,51 @@ class YangToSourcesProcessor { } // We need to instantiate all code generators to determine required import resolution mode - final List> codeGenerators = instantiateGenerators(); - final StatementParserMode importMode = determineRequiredImportMode(codeGenerators); - final Optional optReactor = createReactor(importMode, yangFilesInProject); - if (!optReactor.isPresent()) { + final List codeGenerators = instantiateGenerators(); + if (codeGenerators.isEmpty()) { + LOG.warn("{} No code generators provided", LOG_PREFIX); return; } - final ProcessorModuleReactor reactor = optReactor.get(); - if (!skip) { - final Stopwatch watch = Stopwatch.createStarted(); - final ContextHolder holder; + final Set parserModes = codeGenerators.stream() + .map(GeneratorTaskFactory::parserMode) + .collect(Collectors.toUnmodifiableSet()); + + // FIXME: store these files into state, so that we can verify/clean up + final Builder files = ImmutableSet.builder(); + for (StatementParserMode parserMode : parserModes) { + final Optional optReactor = createReactor(yangFilesInProject, parserMode); + if (optReactor.isPresent()) { + final ProcessorModuleReactor reactor = optReactor.orElseThrow(); + + if (!skip) { + final Stopwatch watch = Stopwatch.createStarted(); + final ContextHolder holder; + + try { + holder = reactor.toContext(); + } catch (YangParserException e) { + throw new MojoFailureException("Failed to process reactor " + reactor, e); + } catch (IOException e) { + throw new MojoExecutionException("Failed to read reactor " + reactor, e); + } + + LOG.info("{} {} YANG models processed in {}", LOG_PREFIX, holder.getContext().getModules().size(), + watch); + files.addAll(generateSources(holder, codeGenerators, parserMode)); + } else { + LOG.info("{} Skipping YANG code generation because property yang.skip is true", LOG_PREFIX); + } - try { - holder = reactor.toContext(); - } catch (YangParserException e) { - throw new MojoFailureException("Failed to process reactor " + reactor, e); - } catch (IOException e) { - throw new MojoExecutionException("Failed to read reactor " + reactor, e); + // FIXME: this is not right: we should be generating the models exactly once! + // add META_INF/yang + final Collection models = reactor.getModelsInProject(); + try { + yangProvider.addYangsToMetaInf(project, models); + } catch (IOException e) { + throw new MojoExecutionException("Failed write model files for " + models, e); + } } - - LOG.info("{} {} YANG models processed in {}", LOG_PREFIX, holder.getContext().getModules().size(), watch); - generateSources(holder, codeGenerators); - } else { - LOG.info("{} Skipping YANG code generation because property yang.skip is true", LOG_PREFIX); - } - - // add META_INF/yang - final Collection models = reactor.getModelsInProject(); - try { - yangProvider.addYangsToMetaInf(project, models); - } catch (IOException e) { - throw new MojoExecutionException("Failed write model files for " + models, e); } // add META_INF/services @@ -175,111 +188,89 @@ class YangToSourcesProcessor { META_INF_YANG_SERVICES_STRING_JAR); } - private static StatementParserMode determineRequiredImportMode( - final Collection> codeGenerators) throws MojoFailureException { - ImportResolutionMode requestedMode = null; - BasicCodeGenerator requestingGenerator = null; - - for (Entry entry : codeGenerators) { - final BasicCodeGenerator generator = entry.getValue(); - final ImportResolutionMode mode = generator.getImportResolutionMode(); - if (mode == null) { - // the generator does not care about the mode - continue; - } - - if (requestedMode == null) { - // No mode yet, we have just determined it - requestedMode = mode; - requestingGenerator = generator; - continue; - } - - if (mode != requestedMode) { - throw new MojoFailureException(String.format( - "Import resolution mode conflict between %s (%s) and %s (%s)", requestingGenerator, requestedMode, - generator, mode)); - } - } - - if (requestedMode == null) { - return StatementParserMode.DEFAULT_MODE; - } - switch (requestedMode) { - case REVISION_EXACT_OR_LATEST: - return StatementParserMode.DEFAULT_MODE; - case SEMVER_LATEST: - return StatementParserMode.SEMVER_MODE; - default: - throw new IllegalStateException("Unhandled import resolution mode " + requestedMode); + private List instantiateGenerators() throws MojoExecutionException, MojoFailureException { + final List generators = new ArrayList<>(codeGeneratorArgs.size()); + for (CodeGeneratorArg arg : codeGeneratorArgs) { + generators.add(CodeGeneratorTaskFactory.create(arg)); + LOG.info("{} Code generator instantiated from {}", LOG_PREFIX, arg.getCodeGeneratorClass()); } - } - private List> instantiateGenerators() throws MojoExecutionException { - final List> generators = new ArrayList<>(codeGeneratorArgs.size()); - for (CodeGeneratorArg arg : codeGeneratorArgs) { - arg.check(); + // Search for available FileGenerator implementations + final Map factories = Maps.uniqueIndex( + ServiceLoader.load(FileGeneratorFactory.class), FileGeneratorFactory::getIdentifier); + + // Assign instantiate FileGenerators with appropriate configurate + for (Entry entry : factories.entrySet()) { + final String id = entry.getKey(); + FileGeneratorArg arg = fileGeneratorArgs.get(id); + if (arg == null) { + LOG.debug("{} No configuration for {}, using empty", LOG_PREFIX, id); + arg = new FileGeneratorArg(id); + } - final BasicCodeGenerator generator; try { - generator = getInstance(arg.getCodeGeneratorClass(), BasicCodeGenerator.class); - } catch (ReflectiveOperationException e) { - throw new MojoExecutionException("Failed to instantiate code generator " - + arg.getCodeGeneratorClass(), e); + generators.add(FileGeneratorTaskFactory.of(entry.getValue(), arg)); + } catch (FileGeneratorException e) { + throw new MojoExecutionException("File generator " + id + " failed", e); } - - LOG.info("{} Code generator instantiated from {}", LOG_PREFIX, arg.getCodeGeneratorClass()); - generators.add(new SimpleImmutableEntry<>(arg, generator)); + LOG.info("{} Code generator {} instantiated", LOG_PREFIX, id); } return generators; } @SuppressWarnings("checkstyle:illegalCatch") - private Optional createReactor(final StatementParserMode parserMode, - final List yangFilesInProject) throws MojoExecutionException { + private Optional createReactor(final List yangFilesInProject, + final StatementParserMode parserMode) throws MojoExecutionException { LOG.info("{} Inspecting {}", LOG_PREFIX, yangFilesRootDir); - try { - final Collection allFiles = new ArrayList<>(yangFilesInProject); - final Collection dependencies; - if (inspectDependencies) { - dependencies = new ArrayList<>(); - final Stopwatch watch = Stopwatch.createStarted(); + final Collection allFiles = new ArrayList<>(yangFilesInProject); + final Collection dependencies; + if (inspectDependencies) { + dependencies = new ArrayList<>(); + final Stopwatch watch = Stopwatch.createStarted(); + + try { ScannedDependency.scanDependencies(project).forEach(dep -> { allFiles.add(dep.file()); dependencies.add(dep); }); - LOG.info("{} Found {} dependencies in {}", LOG_PREFIX, dependencies.size(), watch); - } else { - dependencies = ImmutableList.of(); - } - - /* - * Check if any of the listed files changed. If no changes occurred, - * simply return null, which indicates and of execution. - */ - final Stopwatch watch = Stopwatch.createStarted(); - if (!allFiles.stream().anyMatch(buildContext::hasDelta)) { - LOG.info("{} None of {} input files changed", LOG_PREFIX, allFiles.size()); - return Optional.empty(); + } catch (IOException e) { + LOG.error("{} Failed to scan dependencies", LOG_PREFIX, e); + throw new MojoExecutionException(LOG_PREFIX + " Failed to scan dependencies ", e); } + LOG.info("{} Found {} dependencies in {}", LOG_PREFIX, dependencies.size(), watch); + } else { + dependencies = ImmutableList.of(); + } - final YangParser parser = parserFactory.createParser(parserMode); - final List sourcesInProject = new ArrayList<>(yangFilesInProject.size()); + /* + * Check if any of the listed files changed. If no changes occurred, simply return empty, which indicates + * end of execution. + */ + final Stopwatch watch = Stopwatch.createStarted(); + if (!allFiles.stream().anyMatch(buildContext::hasDelta)) { + LOG.info("{} None of {} input files changed", LOG_PREFIX, allFiles.size()); + return Optional.empty(); + } + try { final List> parsed = yangFilesInProject.parallelStream() .map(file -> { final YangTextSchemaSource textSource = YangTextSchemaSource.forFile(file); try { - return new SimpleImmutableEntry<>(textSource, - TextToIRTransformer.transformText(textSource)); + return Map.entry(textSource,TextToIRTransformer.transformText(textSource)); } catch (YangSyntaxErrorException | IOException e) { throw new IllegalArgumentException("Failed to parse " + file, e); } }) .collect(Collectors.toList()); + // FIXME: all of the above checks need to be done just once + + + final List sourcesInProject = new ArrayList<>(yangFilesInProject.size()); + final YangParser parser = parserFactory.createParser(parserMode); for (final Entry entry : parsed) { final YangTextSchemaSource textSource = entry.getKey(); final IRSchemaSource astSource = entry.getValue(); @@ -328,85 +319,31 @@ class YangToSourcesProcessor { /** * Call generate on every generator from plugin configuration. */ - @SuppressWarnings("checkstyle:illegalCatch") - private void generateSources(final ContextHolder context, - final Collection> generators) throws MojoFailureException { - if (generators.isEmpty()) { - LOG.warn("{} No code generators provided", LOG_PREFIX); - return; - } + private Set generateSources(final ContextHolder context, final Collection generators, + final StatementParserMode parserMode) throws MojoFailureException { + final Builder allFiles = ImmutableSet.builder(); + for (GeneratorTaskFactory factory : generators) { + if (!parserMode.equals(factory.parserMode())) { + continue; + } - final Map thrown = new HashMap<>(); - for (Entry entry : generators) { - final String codeGeneratorClass = entry.getKey().getCodeGeneratorClass(); + final Stopwatch sw = Stopwatch.createStarted(); + final GeneratorTask task = factory.createTask(project, context); + LOG.debug("{} Task {} initialized in {}", LOG_PREFIX, task, sw); + final Collection files; try { - generateSourcesWithOneGenerator(context, entry.getKey(), entry.getValue()); - } catch (Exception e) { - // try other generators, exception will be thrown after - LOG.error("{} Unable to generate sources with {} generator", LOG_PREFIX, codeGeneratorClass, e); - thrown.put(codeGeneratorClass, e.getClass().getCanonicalName()); + files = task.execute(buildContext); + } catch (FileGeneratorException | IOException e) { + throw new MojoFailureException(LOG_PREFIX + " Generator " + factory + " failed", e); } - } - if (!thrown.isEmpty()) { - LOG.error("{} One or more code generators failed, including failed list(generatorClass=exception) {}", - LOG_PREFIX, thrown); - throw new MojoFailureException(LOG_PREFIX - + " One or more code generators failed, including failed list(generatorClass=exception) " + thrown); + LOG.debug("{} Sources generated by {}: {}", LOG_PREFIX, factory.generatorName(), files); + LOG.info("{} Sources generated by {}: {} in {}", LOG_PREFIX, factory.generatorName(), + files == null ? 0 : files.size(), sw); + allFiles.addAll(files); } - } - - /** - * Complete initialization of a code generator and invoke it. - */ - private void generateSourcesWithOneGenerator(final ContextHolder context, final CodeGeneratorArg codeGeneratorCfg, - final BasicCodeGenerator codeGenerator) throws IOException { - final File outputDir = requireNonNull(codeGeneratorCfg.getOutputBaseDir(project), - "outputBaseDir is null. Please provide a valid outputBaseDir value in pom.xml"); - - project.addCompileSourceRoot(outputDir.getAbsolutePath()); - - LOG.info("{} Sources will be generated to {}", LOG_PREFIX, outputDir); - LOG.debug("{} Project root dir is {}", LOG_PREFIX, project.getBasedir()); - LOG.debug("{} Additional configuration picked up for : {}: {}", LOG_PREFIX, codeGeneratorCfg - .getCodeGeneratorClass(), codeGeneratorCfg.getAdditionalConfiguration()); - - if (codeGenerator instanceof BuildContextAware) { - ((BuildContextAware)codeGenerator).setBuildContext(buildContext); - } - if (codeGenerator instanceof MavenProjectAware) { - ((MavenProjectAware)codeGenerator).setMavenProject(project); - } - codeGenerator.setAdditionalConfig(codeGeneratorCfg.getAdditionalConfiguration()); - - File resourceBaseDir = codeGeneratorCfg.getResourceBaseDir(project); - YangProvider.setResource(resourceBaseDir, project); - codeGenerator.setResourceBaseDir(resourceBaseDir); - LOG.debug("{} Folder: {} marked as resources for generator: {}", LOG_PREFIX, resourceBaseDir, - codeGeneratorCfg.getCodeGeneratorClass()); - - if (outputDir.exists()) { - Files.walk(outputDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); - LOG.info("{} Succesfully deleted output directory {}", LOG_PREFIX, outputDir); - } - final Stopwatch watch = Stopwatch.createStarted(); - Collection generated = codeGenerator.generateSources(context.getContext(), outputDir, - context.getYangModules(), context); - - LOG.debug("{} Sources generated by {}: {}", LOG_PREFIX, codeGeneratorCfg.getCodeGeneratorClass(), generated); - LOG.info("{} Sources generated by {}: {} in {}", LOG_PREFIX, codeGeneratorCfg.getCodeGeneratorClass(), - generated == null ? 0 : generated.size(), watch); - } - - /** - * Instantiate object from fully qualified class name. - */ - private static T getInstance(final String codeGeneratorClass, final Class baseType) - throws ReflectiveOperationException { - final Class clazz = Class.forName(codeGeneratorClass); - checkArgument(baseType.isAssignableFrom(clazz), "Code generator %s has to implement %s", clazz, baseType); - return baseType.cast(clazz.getConstructor().newInstance()); + return allFiles.build(); } } diff --git a/plugin/yang-maven-plugin/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesMojoTest.java b/plugin/yang-maven-plugin/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesMojoTest.java index 43faea0b25..cf32486272 100644 --- a/plugin/yang-maven-plugin/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesMojoTest.java +++ b/plugin/yang-maven-plugin/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesMojoTest.java @@ -7,6 +7,10 @@ */ package org.opendaylight.yangtools.yang2sources.plugin; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + import com.google.common.collect.ImmutableList; import java.io.File; import java.util.ArrayList; @@ -14,7 +18,6 @@ import java.util.List; import org.apache.maven.model.Build; import org.apache.maven.model.Plugin; import org.apache.maven.project.MavenProject; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -39,29 +42,29 @@ public class YangToSourcesMojoTest { @Test public void yangToSourceMojoTest() throws Exception { - Mockito.when(this.project.getPlugin(YangToSourcesMojo.PLUGIN_NAME)).thenReturn(this.plugin); + doReturn(plugin).when(project).getPlugin(YangToSourcesMojo.PLUGIN_NAME); this.mojo = new YangToSourcesMojo(); - this.mojo.setProject(this.project); + this.mojo.setProject(project); this.mojo.buildContext = new DefaultBuildContext(); this.mojo.execute(); - Assert.assertNotNull(this.mojo); + assertNotNull(this.mojo); final YangToSourcesProcessor processor = Mockito.mock(YangToSourcesProcessor.class); this.mojo = new YangToSourcesMojo(processor); - this.mojo.setProject(this.project); + this.mojo.setProject(project); this.mojo.execute(); - Mockito.verify(processor).conditionalExecute(false); + verify(processor).conditionalExecute(false); } @Test public void test() throws Exception { prepareProcessor(); - Assert.assertNotNull(this.proc); - this.mojo = new YangToSourcesMojo(this.proc); - this.mojo.setProject(this.project); + assertNotNull(proc); + this.mojo = new YangToSourcesMojo(proc); + this.mojo.setProject(project); this.mojo.execute(); - Assert.assertNotNull(this.mojo); + assertNotNull(mojo); } private void prepareProcessor() { @@ -72,11 +75,11 @@ public class YangToSourcesMojoTest { final CodeGeneratorArg codeGeneratorArg = new CodeGeneratorArg(GeneratorMock.class.getName(), "target/YangToSourcesMojoTest-outputBaseDir"); codeGenerators.add(codeGeneratorArg); - final MavenProject mvnProject = Mockito.mock(MavenProject.class); final Build build = new Build(); - Mockito.when(mvnProject.getBuild()).thenReturn(build); + build.setDirectory("testDir"); + doReturn(build).when(project).getBuild(); final boolean dependencies = true; this.proc = new YangToSourcesProcessor(file, ImmutableList.of(excludedYang), codeGenerators, - mvnProject, dependencies, YangProvider.getInstance()); + project, dependencies, YangProvider.getInstance()); } } diff --git a/plugin/yang-maven-plugin/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesProcessorTest.java b/plugin/yang-maven-plugin/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesProcessorTest.java index 2250ed4ddd..23b6f01ee5 100644 --- a/plugin/yang-maven-plugin/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesProcessorTest.java +++ b/plugin/yang-maven-plugin/src/test/java/org/opendaylight/yangtools/yang2sources/plugin/YangToSourcesProcessorTest.java @@ -7,56 +7,64 @@ */ package org.opendaylight.yangtools.yang2sources.plugin; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.doReturn; + import com.google.common.collect.ImmutableList; import java.io.File; import java.util.List; import org.apache.maven.model.Build; import org.apache.maven.project.MavenProject; -import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; +import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.opendaylight.yangtools.yang2sources.plugin.ConfigArg.CodeGeneratorArg; import org.opendaylight.yangtools.yang2sources.plugin.GenerateSourcesTest.GeneratorMock; -@RunWith(MockitoJUnitRunner.Silent.class) +@RunWith(MockitoJUnitRunner.StrictStubs.class) public class YangToSourcesProcessorTest { + @Mock + public File buildContext; + @Mock + public MavenProject project; + @Mock + public YangProvider inspectDependencies; - private final File buildContext = Mockito.mock(File.class); - private final List yangFilesRootDir = ImmutableList.of(buildContext); - private final MavenProject project = Mockito.mock(MavenProject.class); private final boolean dep = false; - private final YangProvider inspectDependencies = Mockito.mock(YangProvider.class); + private List yangFilesRootDir; + + @Before + public void before() { + yangFilesRootDir = ImmutableList.of(buildContext); + } @Test public void yangToSourcesProcessotTest() { - Mockito.when(this.buildContext.getPath()).thenReturn("path"); YangToSourcesProcessor processor = new YangToSourcesProcessor(buildContext, yangFilesRootDir, ImmutableList.of(), project, dep, inspectDependencies); - Assert.assertNotNull(processor); + assertNotNull(processor); processor = new YangToSourcesProcessor(buildContext, yangFilesRootDir, ImmutableList.of(), project, dep, inspectDependencies); - Assert.assertNotNull(processor); + assertNotNull(processor); } @Test public void test() throws Exception { final File file = new File(getClass().getResource("/yang").getFile()); final File excludedYang = new File(getClass().getResource("/yang/excluded-file.yang").getFile()); - final String path = file.getPath(); final CodeGeneratorArg codeGeneratorArg = new CodeGeneratorArg(GeneratorMock.class.getName(), "target/YangToSourcesProcessorTest-outputBaseDir"); final List codeGenerators = ImmutableList.of(codeGeneratorArg); - final MavenProject mvnProject = Mockito.mock(MavenProject.class); final Build build = new Build(); - Mockito.when(mvnProject.getBuild()).thenReturn(build); + build.setDirectory("foo"); + doReturn(build).when(project).getBuild(); final boolean dependencies = true; final YangToSourcesProcessor proc = new YangToSourcesProcessor(file, ImmutableList.of(excludedYang), - codeGenerators, mvnProject, dependencies, YangProvider.getInstance()); - Assert.assertNotNull(proc); + codeGenerators, project, dependencies, YangProvider.getInstance()); + assertNotNull(proc); proc.execute(); } - } -- 2.36.6