SourceSet language transformation plugins

Hello,

I am using one of the recent nightly builds of gradle, and I am trying to create a plugin for transforming yacc grammars to C/C++ source code. In the build.gradle file, I would like to be able to have something like:

model {
 components {
            mylibrary(NativeLibrarySpec) {
                binaries.all {
                    cppCompiler.args Config.includePaths(project,"mylibrary")
                }
                sources {
                    cpp {
                        source {
                            include '**/*.cc'
                        }
                      yacc {
                        source { include '**/*.y' }
                    }
                }
            }
        }
    }

but I am having trouble figuring out how to register the LanguageSourceSet factory from the plugin class. I think DefaultPolymorphicDomainObjectContainer is looking for a registered factory for YaccLanguageSourceSet when the ‘yacc’ name is encountered. The error I get is:

Cannot create a LanguageSourceSet named ‘yacc’ because this container does not support creating elements by name alone. Please specify which subtype of LanguageSourceSet to create. Known subtypes are: CppSourceSet, JvmResourceSet, YaccLanguageSourceSet

I have used the Scala plugin as a model for how to structure the plugin et al. but right now I am just trying to figure out how to have a plugin which gets a SourceSet from the build.gradle file on a per-cpp-library basis. FWIW my plugin code is below. I know the JVM compilation model is wrong for YACC, but the scala plugin seemed to be well ordered and I am really just trying to get a ‘*.y’ SourceSet where I can (1) pass it through bison and then (2) add the output to the C/C++ SourceSet.

I have a lot of these transformational plugins to do, e.g. XML to C++, XML to LaTeX, etc. so I am really trying to do it right.

Thanks very much for any advice or tips Darrell

class YaccLanguagePlugin implements Plugin<Project> {
      def void apply(Project project) {
        project.getPluginManager().apply(ComponentModelBasePlugin.class);
        project.getPluginManager().apply(JvmResourcesPlugin.class);
    }
      /**
     * Model rules.
     */
    @SuppressWarnings("UnusedDeclaration")
    static class Rules extends RuleSource {
          @Model
        YaccToolChain yaccToolChain(ServiceRegistry serviceRegistry) {
            return serviceRegistry.get(YaccToolChain.class);
        }
          @LanguageType
        void registerLanguage(LanguageTypeBuilder<YaccLanguageSourceSet> builder) {
            builder.setLanguageName("yacc");
            builder.defaultImplementation(DefaultYaccLanguageSourceSet.class);
        }
          @Mutate
        void registerLanguageTransform(LanguageTransformContainer languages, ServiceRegistry serviceRegistry) {
            languages.add(new Yacc());
        }
    }
      private static class Yacc implements LanguageTransform<YaccLanguageSourceSet, JvmByteCode> {
        public Class<YaccLanguageSourceSet> getSourceSetType() {
            return YaccLanguageSourceSet.class;
        }
          public Map<String, Class<?>> getBinaryTools() {
            return Collections.emptyMap();
        }
          public Class<JvmByteCode> getOutputType() {
            return JvmByteCode.class;
        }
          public SourceTransformTaskConfig getTransformTask() {
            return new SourceTransformTaskConfig() {
                public String getTaskPrefix() {
                    return "compile";
                }
                  public Class<? extends DefaultTask> getTaskType() {
                    return PlatformYaccCompile.class;
                }
                  public void configureTask(Task task, BinarySpec binarySpec, LanguageSourceSet sourceSet) {
                    PlatformYaccCompile compile = (PlatformYaccCompile) task;
                    YaccLanguageSourceSet yaccSourceSet = (YaccLanguageSourceSet) sourceSet;
                    JvmBinarySpec binary = (JvmBinarySpec) binarySpec;
                    JavaPlatform javaPlatform = binary.getTargetPlatform();
                    // TODO RG resolve the yacc platform from the binary
                      compile.setPlatform(new DefaultYaccPlatform("2.10.4"));
                    File analysisFile = new File(task.getTemporaryDir(), String.format("compilerAnalysis/%s.analysis", task.getName()));
                    compile.getYaccCompileOptions().getIncrementalOptions().setAnalysisFile(analysisFile);
                      compile.setDescription(String.format("Compiles %s.", yaccSourceSet));
                    compile.setDestinationDir(binary.getClassesDir());
                      compile.setSource(yaccSourceSet.getSource());
                    compile.setClasspath(yaccSourceSet.getCompileClasspath().getFiles());
                    compile.setTargetCompatibility(javaPlatform.getTargetCompatibility().toString());
                    compile.setSourceCompatibility(javaPlatform.getTargetCompatibility().toString());
                      compile.dependsOn(yaccSourceSet);
                    binary.getTasks().getJar().dependsOn(compile);
                }
            };
        }
          public boolean applyToBinary(BinarySpec binary) {
            return binary instanceof JvmBinarySpec;
        }
    }
}

Hey,
wow, you seem to be a very very early adaptor :slight_smile:

the registration of your yacc SourceSet-Type is done in your @LanguageType void registerLanguage(…) method.
But this does not yet create a sourceSet for you. To create a sourceSet you have to pass the type of the interface. you can do that in your build script by something like this:

model {    
    components {
        main {
            sources {
                yacc(YaccLanguageSourceSet)
            }
        }
    }
cheers,
René


Fantastic! I’ll do that. Thanks for the quick feedback…

How does the C++ source avoid having to specify the CppSourceSet type, e.g. when I do:

model { components { main { sources { cpp { source include '**/*.cc' } } } } }

instead of:

model { components { main { sources { cpp(CppSourceSet) { source include '**/*.cc' } } } } }

thanks again, Darrell

Let me explain you why you don’t need to declare the type for the CppSourceSet.
In the snippet you mention:

model { components { main { sources { cpp { source include ‘**/*.cc’ } } } } }
you don’t declare a new sourceSet but configure an existing one. You wonder where that CppSourceSet named ‘cpp’ comes from? It is created by gradle for you. The reason is that you declared a ‘NativeLibrarySpec’ component, which takes ObjectFile as inputs. For all languages that are registered in the language-registry which have ObjectFile declared as their output an according sourceSet is created.

If you would change your Yacc class’ getOutputType to return ObjectFile.class you should see that the yacc sourceSet is automatically created for you.

cheers,
René

Thanks very much!