-
Notifications
You must be signed in to change notification settings - Fork 114
Migration to Bazel
@regisd is attempting to migrate the build of JFlex from Maven to Bazel.
Disclaimer very opinionated, you are free to think otherwise.
JFlex is currently built with the Maven build system. Maven had good promises (central repository of jars, dependency management, conventional build steps, well defined build, etc.). And the central repository has been great value to the whole Java ecosystem.
However, Maven isn't satisfactory in many other ways.
However, Maven failed short in many areas:
- As long as the project is simple, everything goes well. But if when a project can't follow the convention, it's a long fight with the configuration of the plugins.
- The promise of having a reproducible build is a lie. The Maven plugins become incompatible with every upgrade of Java or Maven.
- The XML based configuration is extremely verbose.
- The documentation was extremely lacking or outdated.
- Incremental builds are inexistant or broken
If we ask to run the test suite, Maven will run all tests are executed again, even if the code is unchanged. And the testsuite takes time — around 10 minutes on Travis.
Bazel which was open-sourced by Google in 2015, after being used internally at scale for years.
Bazel manages the graph dependency very well. If a test in added, or the documentation is modified, the other tests run in a couple seconds because their execution is cached. That's why @regisd is attempting to migrate the everything to Bazel.
- Maven is the main build system. Migration in progress.
- Add Bazel BUILD on JFlex modules, one by one. Status ✔ JFlex can be compiled with bazel.
- Build Unicode properties with Maven. Status [WIP]
- Migrate test suite to Bazel Status [WIP]
- Bazel main build system.
- Bazel is the main build system used by developers. Maven is kept as a backup.
- People who want to simply use jflex, can download the targz distribution. The targz doesn't contain java sources.
- People who want to build clone the git repo and use bazel.
- Bazel unique build system.
- Only the Maveb plugin is built with Maven.
- POM is removed.
- testcases are removed
- maven-uniocde-plugin is removed
- Optional: reorganise directory structure.
See project Bazel
- Initial attempt of using Travis PR #395
- Currently using Cirrus
difficulty: very easy
Status ✔ PR #401
See bazel_rules
difficulty: very hard
status: not started, probably the only piece that will stay as a Maven artifact.
Ideally, we need to infer the deps to generate a pom.xml
difficulty: easy
Status ✔ on "simple". PR #401
difficulty: Very easy
Status ✔ PR #442
difficulty: easy (once cup rule and jflex rule are done)
Status ✔ PR #444
Replace jflex-testsuite-maven-plugin and the test case by a testsuite made of java_test
status: WIP (More below)
- some tests have been migrated already
- tests of golden input/output can be migrated automatically with migration tool
- tests that assert that an exception in the jflex generation can use a simple
@JflexTestRunner
If you want to contribute, here is how to migrate a test from maven-based testsuite/testcases to bazel-based javatests/jflex/testcase.
The migration tool can help you migrate a test case, still using input and output golden files.
For instance, to migrate "caseless-jflex":
- Use migration tool
# You need to indicate the absolute path of the maven-based case: bazel run java/jflex/migration:migrator -- ~/Projects/jflex/testsuite/testcases/src/test/cases/caseless-jflex
- Copy the files, as the tool tells you, e.g.
# cp -r /tmp/caseless_jflex ~/Projects/jflex/javatests/jflex/testcase
- Commit as is
- Review the changes, update the javadoc if it points to an old bug in sourceforge, and test that the case passes
bazel test //javatests/jflex/testcase/...
- Commit and send for review
There are a few more steps to migrate a test to a unit test not relying on golden files, and PR #592 on testcase/caseless is a simple example of how the GoldenTest was transformed into a unit test.
- Generate a scanner from the flex file with the
jflex
rule, by creating a build target (this is done automatically by the migrator).- Example for
bol.flex
:jflex( name = "gen_bol_scanner", # follow the convention gen_<filename>_scanner srcs = ["bol.flex"], # the source itself jflex_bin = "//jflex:jflex_bin", # Important! outputs = ["BolScanner.java"], # as defined byy the class )
-
Important Be sure to point to the current
jflex_bin
, otherwise the test will run against the (previous) released version. - Also, define the java package corresponding to the directory of this test
package jflex.testcase.bol; %% // rest of the grammar
- Example for
- Define a scanner state / token
- With the
jflex-testsuite-maven-plugin
, the test was defined with an input file and an expected output onSystem.out
where each lexer action was using aSystem.println
statement. A test should be on the API, not on the console debug output, so we will migrate to unit tests. - Update the flex definition to output a state instead.
%type State
- Create a
State.java
enum with the statespublic enum State { HELLO_AT_BOL_AND_EOL, HELLO_AT_EOL, }
- Modify the flex grammar to return this states.
Replace
by the newly defined states
^"hello"$ { System.out.println("hello at BOL and EOL"); } "hello"$ { System.out.println("hello at BOL"); }
^"hello"$ { return State.HELLO_AT_BOL_AND_EOL; } "hello"$ { return State.HELLO_AT_EOL; }
- Optionally, also add an EOF state in the grammar:
<<EOF>> { return State.END_OF_FILE; }
- With the
- Use unit tests to verify the scanner.
- Add a standard jUnit class, e.g.
BolTest.java
. - Create tests from the old
.input
and.output
files, as deemed appropriate. For instance, the linewith expected output␣␣hello
could be migrated to a single testline: 2 col: 1 char: 6 match: -- -- action [27] { System.out.println( "\" \"" ); } " " line: 2 col: 2 char: 7 match: -- -- action [27] { System.out.println( "\" \"" ); } " " line: 2 col: 3 char: 8 match: --hello-- action [21] { System.out.println("hello at EOL"); } hello at EOL line: 2 col: 8 char: 13 match: --\u000A-- action [25] { System.out.println("\\n"); } \n
@Test public void eol() throws Exception { scanner = createScanner(" hello\n"); assertThat(scanner.yylex()).isEqualTo(State.SPACE); assertThat(scanner.yylex()).isEqualTo(State.SPACE); assertThat(scanner.yylex()).isEqualTo(State.HELLO_AT_EOL); assertThat(scanner.yylex()).isEqualTo(State.LINE_FEED); }
- Please use Google truth to write the assertions.
- Add a standard jUnit class, e.g.
- Add test target (the migration tool created a "GoldenTest", just rename)
java_test( name = "BolTest", srcs = [ "BolTest.java", "State.java", ":bol_scanner", ], deps = [ "//third_party/com/google/truth", ], )
Of course, this cannot be applied if the generation of the scanner is expected to fail: We can't break the build to make a test pass.
In that case, you can use a custom https://github.com/jflex-de/jflex/blob/master/java/jflex/testing/testsuite/JFlexTestRunner.java and write a test in just a few lines of codes.
Example: EofPipeActionTest.
- ccl_pre with JDK variants. ccl-pre/ccl2.test was not migrated. Bazel makes it hard to change the runtime.
-
cup2private
JFlex has a
%cup2
directive, but I don't know where to get cup2 itself. There is apparently no release. -
encoding.
Bazel makes it hard to
change the encoding used by javac.
It's UTF-8 by default.
And the use of
--encoding
changes both the input.flex
and the generated.java
files. -
filename (#399) because Bazel doesn't allow
\
anywaysBUILD:8:1: //javatests/jflex/testcase/filename:FilenameTest: invalid label 'filename\u000AFILE_NAMES_MUST_BE_ESCAPED\u000A.flex' in element 0 of attribute 'data' in 'java_test' rule: invalid target name 'filename\u000AFILE_NAMES_MUST_BE_ESCAPED\u000A.flex': target names may not contain ''
- java because the tests are large integration tests, and it's unclear to me what is their benefit.
status: native behavior of Bazel for java_bin
.
Anyone who wants to build from source clones the git repo and uses Maven.
The source code directory structure looks like this:
├── bazel-* [REMOVED for targz]
├── cup-maven-plugin [OBSOLETE, removed]
├── bin [MOVED from jflex/bin]
├── docs [OK]
├── examples [MOVED from jflex/examples]
├── jflex [OK]
│ ├── common-testing [REMOVED for targz]
│ └── src [OK]
├── jflex-maven-plugin [REMOVED for targz]
├── jflex-unicode-maven-plugin [REMOVED for targz]
├── report-module [OBSOLETE, removed]
├── scripts [REMOVED for targz]
├── src [OBSOLETE, removed]
├── testsuite [REMOVED for targz]
│ ├── bzltestsuite [NEW, replaces jflex-testsuite-maven-plugin]
│ ├── jflex-testsuite-maven-plugin [OBSOLETE, removed]
│ └── testcases
├── lib [UNCHANGED]
├── third_party [NEW]
└── tools [REMOVED for targz]
As a result, the targz has the following strucuture:
Distribution targz
├── bin
│ ├── jflex.bat
│ └── jflex.sh
├── docs [COPIED from bazel-bin]
│ ├── manual.html
│ └── manual.pdf
├── examples
├── jflex
│ └── src [OK]
├── lib [UNCHANGED; add jars copied from bazel-bin]
├── third_party
│ ├── cup
│ ├── com
│ └── org [etc.]
└── README.md LICENSE etc.
Note: for the distribution to avoid the bootstrap question, we can also copy relevant files from bazel-genfiles
:
bazel-genfiles
├── examples
│ └── simple
│ └── Yylex.java
└── jflex
├── LexParse.java
├── LexScan.java
└── sym.java
This is the flattest structure that works well with Maven and makes releasing the targz easy.
├── bin
│ └── jflex.bat
├── docs
│ ├── grammar.md
│ ├── intro.md
│ └── template.tex
├── java
│ └── jflex // consider splitting de.flex.* for tools and jflex.* for core generator
│ ├── LexGenerator.java
│ ├── gui
│ ├── lexparse.flex
│ ├── lexscan.cup
│ ├── maven
│ │ └── JflexMojo.java
│ ├── testing
│ │ └── testsuite
│ │ └── TestRunner.java
│ └── unicode
│ └── UnicodeProperies.java
├── javatests
│ └── jflex
│ └── testsuite
│ └── apiprivate
│ ├── ApiPrivateTest.java
│ └── private.lex
├── scripts
│ ├── release.sh
│ └── travis.sh
└── third_party
├── com
│ └── google
│ └── guava
└── java
└── java_cup
├── java_cup-11b.jar
└── runtime
├── ComplexSymbolFactory.java
└── lr_parser.java