Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grails: why is the Config.groovy file executed during compilation?

From the Grails documentation and this question:

For general configuration Grails provides two files:

  1. grails-app/conf/BuildConfig.groovy
  2. grails-app/conf/Config.groovy

Both of them use Groovy's ConfigSlurper syntax. The first, BuildConfig.groovy, is for settings that are used when running Grails commands, such as compile, doc, etc. The second file, Config.groovy, is for settings that are used when your application is running. This means that Config.groovy is packaged with your application, but BuildConfig.groovy is not.

And here is an extract about the log4j framework:

Grails uses its common configuration mechanism to provide the settings for the underlying Log4j log system, so all you have to do is add a log4j setting to the file grails-app/conf/Config.groovy.

I have a project with these two files: grails-app/conf/BuildConfig.groovy and grails-app/conf/Config.groovy. The project uses the log4j utility, so some settings (including a function) are placed in the Config.groovy file. Now, according to the documentation I quoted, it is the right file, since I want to use the logging utilities for running the application, not compiling it.

What's interesting, these settings are used when I run mvn package on my project - the function inside the log4j settings is executed.

According to both the documentation and the question this should not be the case.

I know that it is possible to use the grailsApplication to access these settings:

def recipient = grailsApplication.config.foo.bar.hello

So I searched my project and found some usages of the grailsApplication, but none related to the log4j settings.

What are other possible reasons of having the log4j settings that are placed in the Config.groovy file used during mvn package? What am I missing?

Update: the mentioned configuration seems to work when I use mvn package to build my project for the first time. Next time I run mvn package, the log4j configuration from the Config.groovy file is used. And if I delete the job workspace, it works well again.

like image 715
user2738748 Avatar asked Sep 18 '17 12:09

user2738748


2 Answers

There is one important thing worth mentioning. Even though Config.groovy is used by application at the runtime, it has to be compiled to Java's Config.class in the first place. When you package your application compiler has to access this file and compile it to the bytecode. Take a look at the listing I've pasted below, it comes from one Grails application I have in my workspace:

Command:

ls -l target/classes | awk '{print $8}'

Ouptput:

application.properties
BootStrap.class
BootStrap$_closure1.class
BootStrap$_closure2.class
BuildConfig.class
BuildConfig$_run_closure1.class
BuildConfig$_run_closure1_closure2.class
BuildConfig$_run_closure1_closure3.class
BuildConfig$_run_closure1_closure4.class
BuildConfig$_run_closure1_closure5.class
com
Config.class
Config$_run_closure1.class
Config$_run_closure1_closure4.class
Config$_run_closure1_closure4_closure5.class
Config$_run_closure1_closure4_closure5_closure6.class
Config$_run_closure2.class
Config$_run_closure2_closure7.class
Config$_run_closure2_closure8.class
Config$_run_closure3.class
DataSource.class
DataSource$_run_closure1.class
DataSource$_run_closure2.class
DataSource$_run_closure3.class
DataSource$_run_closure3_closure4.class
DataSource$_run_closure3_closure4_closure7.class
DataSource$_run_closure3_closure5.class
DataSource$_run_closure3_closure5_closure8.class
DataSource$_run_closure3_closure6.class
DataSource$_run_closure3_closure6_closure9.class
DataSource$_run_closure3_closure6_closure9_closure10.class
resources.class
resources$_run_closure1.class
UrlMappings.class
UrlMappings$__clinit__closure1.class
UrlMappings$__clinit__closure1_closure2.class
UrlMappings$__clinit__closure1_closure2_closure3.class

You can see that Config.groovy file was compiled to Config.class file and all closures that were used inside Config.groovy were compiled to Java's anonymous classes (e.g. Config$_run_closure1_closure4.class). That's why if you put some code that executes any logic to Config.groovy you have to expect it will be compiled and executed since compiled class extends groovy.lang.Script and it executes body of a Groovy script file. Below you can find what does Groovy.class file looks like:

Command:

javap -l  target/classes/Config.class

Output:

Compiled from "Config.groovy"
public class Config extends groovy.lang.Script {
  public static transient boolean __$stMC;

  public static long __timeStamp;

  public static long __timeStamp__239_neverHappen1501076781354;

  public Config();
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          4       4     0  this   LConfig;

  public Config(groovy.lang.Binding);
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          4      21     0  this   LConfig;
          4      21     1 context   Lgroovy/lang/Binding;

  public static void main(java.lang.String...);
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      19     0  args   [Ljava/lang/String;

  public java.lang.Object run();
    LineNumberTable:
      line 14: 4
      line 17: 43
      line 18: 126
      line 24: 194
      line 26: 233
      line 30: 296
      line 31: 323
      line 38: 376
      line 42: 419
      line 45: 453
      line 63: 473
      line 65: 507
      line 68: 550
      line 70: 595
      line 72: 631
      line 74: 679
      line 77: 724
      line 80: 777
      line 84: 822
      line 86: 867
      line 88: 912
      line 99: 932
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0     964     0  this   LConfig;

  public java.lang.Object this$dist$invoke$3(java.lang.String, java.lang.Object);
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      68     0  this   LConfig;
          0      68     1  name   Ljava/lang/String;
          0      68     2  args   Ljava/lang/Object;

  public void this$dist$set$3(java.lang.String, java.lang.Object);
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      53     0  this   LConfig;
          0      53     1  name   Ljava/lang/String;
          0      53     2 value   Ljava/lang/Object;

  public java.lang.Object this$dist$get$3(java.lang.String);
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      46     0  this   LConfig;
          0      46     1  name   Ljava/lang/String;

  protected groovy.lang.MetaClass $getStaticMetaClass();

  public static void __$swapInit();

  static {};

  public int super$1$hashCode();

  public void super$3$printf(java.lang.String, java.lang.Object);

  public void super$3$printf(java.lang.String, java.lang.Object[]);

  public void super$3$setProperty(java.lang.String, java.lang.Object);

  public boolean super$1$equals(java.lang.Object);

  public void super$1$finalize();

  public groovy.lang.Binding super$3$getBinding();

  public void super$3$print(java.lang.Object);

  public void super$3$setBinding(groovy.lang.Binding);

  public java.lang.Object super$3$evaluate(java.io.File);

  public java.lang.String super$1$toString();

  public java.lang.Object super$3$evaluate(java.lang.String);

  public void super$2$setMetaClass(groovy.lang.MetaClass);

  public void super$1$notify();

  public java.lang.Object super$3$invokeMethod(java.lang.String, java.lang.Object);

  public java.lang.Object super$1$clone();

  public void super$1$wait(long, int);

  public void super$1$wait(long);

  public void super$1$wait();

  public groovy.lang.MetaClass super$2$getMetaClass();

  public java.lang.Class super$1$getClass();

  public void super$3$run(java.io.File, java.lang.String[]);

  public void super$3$println(java.lang.Object);

  public void super$1$notifyAll();

  public java.lang.Object super$3$getProperty(java.lang.String);

  public void super$3$println();

  static java.lang.Class class$(java.lang.String);
}

Update

Now to understand why Config.groovy is executed we need to dig into what happens when grails package is being executed. Running grails package command makes _GrailsPackage.groovy script from grails-core being executed. packageApp target calls GrailsProjectPackager.packageApplication()

https://github.com/grails/grails-core/blob/2.4.x/grails-scripts/src/main/scripts/_GrailsPackage.groovy#L48

This method calls createConfig() helper class:

https://github.com/grails/grails-core/blob/2.4.x/grails-project-api/src/main/groovy/org/codehaus/groovy/grails/project/packaging/GrailsProjectPackager.groovy#L274

In createConfig() method implementation we can find ConfigSlurper.parse(script) execution:

https://github.com/grails/grails-core/blob/2.4.x/grails-project-api/src/main/groovy/org/codehaus/groovy/grails/project/packaging/GrailsProjectPackager.groovy#L345

This is a Groovy class that parses a script and in the end it calls script.run() on the script that was parsed. It's easy to find out with a debugger - check my video where I show how to do it in this particular example: https://www.youtube.com/watch?v=s2PN6TjFjUI

I hope it helps.

like image 187
Szymon Stepniak Avatar answered Oct 23 '22 04:10

Szymon Stepniak


mvn package will run other maven phases and that includes the test phase. Grails integration tests will bootstrap your full app so the Config.groovy file will be parsed.

like image 1
bassmartin Avatar answered Oct 23 '22 04:10

bassmartin