Использование внутренних опций и собственные флаги
В этой статье мы разберем, как устроен общий цикл работы Google Closure Compiler, научимся добавлять новые флаги и устанавливать при их помощи внутренние, недокументированные опции компилятора.
Warning: статья частично устарела.
Следующая диаграмма иллюстрирует механизм инициализации и запуска компилятора.
Точкой входа является CompilerRunner (наследует AbstractCompilerRunner ).
Именно в этом классе (преимущественно, в коде родителя) происходит создание основных объектов, чтение флагов, указанных в командной строке и задание опций.
Класс Compiler осуществляет процесс компиляции и сжатия javascript. По умолчанию он использует GoogleCodingConvention - вспомогательный класс для чтения информации о коде из имен.
Для того, чтобы произвести компиляцию и оптимизацию, Google Closure Compiler получает информацию из трех основных источников:
- Собственно код javascript, на основе которого строится граф взаимодействий и синтаксическое дерево разбора.
- Аннотации к коду, например
/** @const */ . Парсер читает их и добавляет информацию в синтаксическое дерево к аннотированному объекту в виде специальной структуры JSDoc .
- Соглашения о коде. Например, константы пишутся большими буквами, экспортируемые (доступные извне) свойства начинаются с подчеркивания "_", приватные свойства заканчиваются подчеркиванием и
т.п. (это в Google Coding Convention).
Разные части компилятора по-своему используют эту информацию. Важно то, что она сопровождает синтаксическое дерево кода и поэтому всегда доступна.
При создании компилятора AbstractCompilerRunner ставит ему ставится уровень логирования по флагу --logging_level . Для логирования используется стандартный java logger. При указании уровня
FINEST, будут выведены все стадии компиляции.
Однако, этот уровень логирования относится только к компилятору. Информация, которую выводят проходы компилятора, например какая переменная была на какую заменена, какая функция заинлайнена - не будет выводится.
Чтобы установить глобальный уровень логирования, в код можно добавить соответствующую инициализацию LogManager .
Новые флаги можно добавлять в CompilerRunner аналогично уже описанным (см. FLAG_... ).
Для этого от CompilerRunner лучше унаследовать. Это очень удобно, т.к. CompilerRunner является единственной точкой входа и специально задуман быть расширяемым.
В классе-наследнике можно изменять опции компилятора, устанавливать недокументированные опции, а также указать собственный класс со стандартами кодирования вместо GoogleCodingConvention .
В объекте опций компилятора CompilerOptions содержатся флаги (булевы переменные) - какие оптимизации делать, а какие нет.
Эти флаги ставятся непосредственно после создания объекта опций объектом класса CompilationLevel . Его метод setOptionsForCompilationLevel назначает набор опций, исходя из значения --compilation_level .
Вы можете поменять набор или адаптировать его к вашим требованиям.
Например, можно добавить свой проход комплятора для определенного значения опции.
Впрочем, для более удобной поддержки лучше будет модифицировать опции в наследнике CompilerRunner .
Добавим флаг debug_level , который будет устанавливать переопределяемую javascript-переменную DEBUG_LEVEL при компиляции в нужное значение.
Для этого нужно совершить следующие шаги.
Взять код компилятора можно из SVN:
svn co http://closure-compiler.googlecode.com/svn/trunk gc
Создадим рядом с CompilerRunner свой класс-наследник MyCompilerRunner .
Он может быть, например, таким:
package com.google.javascript.jscomp;
import com.google.common.flags.Flag;
import com.google.common.flags.FlagSpec;
import java.io.PrintStream;
public class MyCompilerRunner extends CompilerRunner {
/* Определить новый флаг */
@FlagSpec(help = "Debug level.")
public static final Flag<Integer> FLAG_debug_level
= Flag.value(5);
/* всевозможные конструкторы */
public MyCompilerRunner(String[] args) {
super(args);
}
public MyCompilerRunner(String[] args, PrintStream out, PrintStream err) {
super(args, out, err);
}
/* изменить опции, в соответствие с новым флагом */
@Override
protected CompilerOptions createOptions() {
CompilerOptions options = super.createOptions();
/* переопределение javascript-переменной в значение флага */
options.setDefineToNumberLiteral("DEBUG_LEVEL", FLAG_debug_level.get());
return options;
}
/* точка входа в компилятор */
public static void main(String[] args) {
(new MyCompilerRunner(args)).run();
}
}
Положить этот файл необходимо в ту же директорию, где лежит CompilerRunner.java , то есть в src/com/google/javascript/jscomp .
В этом коде добавлен новый флаг и переопределен метод createOptions , отвечающий за изначальное создание опций компилятора.
Для сборки необходим установленный JDK 1.6 и ANT. И то и другое достаточно просто в установке.
На верхнем уровне исходных кодов компилятора, рядом с директорией src есть файл build.xml . Это файл сборки ANT.
Для того, чтобы точкой входа служил класс MyCompilerRunner , достаточно заменить в ant-файле build.xml слово CompilerRunner на MyCompilerRunner , и затем запустить ant в директории с build.xml .
После этого в директории build появится новый jar-файл компилятора, с новыми флагами и поциями.
Компиляция в IDE
Когда я только начинал разбираться с Google Closure Compiler - сразу же затащил его в проект IDEA. Компиляция и запуск прошла без сучка и задоринки, но новые флаги не работали.
Оказалось, в build.xml прописан процесс перегенерации файла флагов flags.xml . Поэтому при изменении флагов лучше пересобрать компилятор через ant , используя ant plugin или командную строку.
Процессор флагов описан в jar-файле, который доступен в SVN вместе с кодом компилятора. Однако, исходника для этого файла нет.
К счастью, примеры всевозможных флагов есть в самом компиляторе. Можно даже объявить флаг, допускающий многократное указание опций. Такой флаг используется, например, в статье Автоудаление отладочных свойств и объектов.
Расширение компилятора производится аналогично. Добавляется класс MyCompiler , наследующий от Compiler .
Например, включим в унаследованном компиляторе логирование на всю катушку без использования флага java.util.logging.config.file.
Для этого добавим чтение конфига в начало конструктора MyCompiler :
package com.google.javascript.jscomp;
import java.io.*;
import java.util.logging.LogManager;
public class MyCompiler extends Compiler {
// есть и другие конструкторы, но при запуске в консоли используется этот
public MyCompiler(PrintStream stream) {
super(stream);
try {
File f = new File("/tmp/logging.properties");
InputStream in = new FileInputStream(f);
BufferedInputStream bin = new BufferedInputStream(in);
LogManager.getLogManager().readConfiguration(bin);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Файл logging.properties может быть, например, таким:
handlers= java.util.logging.ConsoleHandler
.level= ALL
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
some.class.level = SEVERE
Чтобы задействовать новый класс компилятора, перекроем в MyCompilerRunner соответствующий фабричный метод:
/* фабричный метод для компилятора */
@Override
protected Compiler createCompiler() {
return new MyCompiler(getErrorPrintStream());
}
На практике вы, возможно, захотите модифицировать другие методы компилятора, включая фабричные. Класс Compiler , как и CompilerRunner изначально задуман как расширяемый.
Итак, мы рассмотрели, как, в общих чертах, работает компилятор и изучили безопасный путь добавления новых опций и флагов.
Два рассмотренных класса: Compiler и CompilerRunner проще всего унаследовать и расширить, не нарушая общей структуры и без патчей компилятора.
Как правило, расширения используются для добавления дополнительных проходов и установки своих и недокументированных опций.
Примеры практического применения своих флагов и опций вы можете увидеть в статьях:
Многие опции пока недокументированы и даже недоступны в стандартной сборке. Почему - остается только гадать: работают они вполне эффективно. Видимо, это побочный эффект организации процесса разработки и того, что над Google Closure Compiler идет активная работа.
Следующий класс добавляет ряд опций в виде флагов компилятора.
package com.google.javascript.jscomp;
import com.google.common.flags.Flag;
import com.google.common.flags.FlagSpec;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.List;
public class MyCompilerRunner extends CompilerRunner {
@FlagSpec(help = "Specify stripTypes. You can include multiple.")
public static final Flag<List<String>> FLAG_strip_types
= Flag.stringCollector();
@FlagSpec(help = "Specify stripTypePrefixes. You can include multiple.")
public static final Flag<List<String>> FLAG_strip_type_prefixes = Flag.stringCollector();
@FlagSpec(help = "Specify stripTypeSuffixes. You can include multiple.")
public static final Flag<List<String>> FLAG_strip_name_suffixes
= Flag.stringCollector();
@FlagSpec(help = "Specify stripNamePrefixes. You can include multiple.")
public static final Flag<List<String>> FLAG_strip_name_prefixes
= Flag.stringCollector();
@FlagSpec(help = "@export support.")
public static final Flag<Boolean> FLAG_export_annotation
= Flag.value(true);
@FlagSpec(help = "Enables property renaming: HEURISTIC/AGGRESSIVE_HEURISTIC")
public static final Flag<PropertyRenamingPolicy> FLAG_property_renaming_policy
= Flag.value(PropertyRenamingPolicy.OFF);
/* всевозможные конструкторы */
public MyCompilerRunner(String[] args) {
super(args);
}
public MyCompilerRunner(String[] args, PrintStream out, PrintStream err) {
super(args, out, err);
}
/* изменить опции, в соответствие с новым флагом */
@Override
protected CompilerOptions createOptions() {
CompilerOptions options = super.createOptions();
options.stripTypes = new HashSet<String>(FLAG_strip_types.get());
options.stripTypePrefixes = new HashSet<String>(FLAG_strip_type_prefixes.get());
options.stripNameSuffixes = new HashSet<String>(FLAG_strip_name_suffixes.get());
options.stripNamePrefixes = new HashSet<String>(FLAG_strip_name_prefixes.get());
options.generateExports = FLAG_export_annotation.get();
options.propertyRenaming = FLAG_property_renaming_policy.get();
return options;
}
/* точка входа в компилятор */
public static void main(String[] args) {
(new MyCompilerRunner(args)).run();
}
}
К этому классу, при необходимости, можно добавить свои флаги и опции: externExportsPath , goog.LOCALE , переопределения @define и т.п.
|
Илья, пожалейте простых людей. Выложите готовый jar.
В последних версиях класса CompilerRunner уже нет, статья теряет актуальность. Увы, соответственно, как и другие статьи про эвристическое переименование, ...
он есть, просто переименован.
(ver 119, 2/27/10):
Rename CompilerRunner -> CommandLineRunner.
I'll also do this for JSCompilerRunner, but it will be a much
bigger change. (Nick)
да, но не работает
import com.google.common.flags.Flag;