External Build Process Workflow
When the user invokes an action that involves executing an external build (Make, Build Artifacts, etc.), the following steps happen:
- Before-compile tasks are executed in the IntelliJ process.
- Some source generation tasks that depend on the PSI (e.g. UI designer form to source compilation) are executed in the IntelliJ process.
- BuildTargetScopeProvider extensions are called to calculate the scope of the external build (the set of build targets to compile based on the target module to make and the known set of changes).
- The external build process is spawned (or an existing build process background process is reused).
- The external build process loads the IntelliJ IDEA project model (.idea, .iml files and so on), represented by a JpsModel instance.
- The full tree of targets to build is calculated, based on the dependencies of each build target to be compiled.
- For each target, the set of builders capable of building this target is calculated.
- For every target and every builder, the build() method is called. This can happen in parallel if the "Compile independent modules in parallel" option is enabled in the settings. For module-level builders, the order of invoking builders for a single target is determined by their category; for other builders, the order is undefined.
- Caches to record the state of the compilation are saved.
- Compilation messages reported through the CompileContext API are transmitted to the IntelliJ process and displayed in the UI (in the Messages view).
- Post-compile tasks are executed in the IntelliJ process.
To support incremental build, the build process uses a number of caches which are persisted between build invocations. Even if your compiler doesn't support incremental build, you still need to report correct information so that incremental build works correctly for other compilers.
SourceToOutputMappingis a many-to-many relationship between source files and output files ("which source files were used to produce the specified output file"). It's filled by calls to BuildOutputConsumer.registerOutputFile() and ModuleLevelBuilder.OutputConsumer.registerOutputFile().
Timestampsrecords the timestamp of each source file when it was compiled by each build target. (If the compilation was invoked multiple times with different scopes and the file was changed inbetween, the last compiled timestamps for different build targets may vary.) It's updated automatically by JPS.
IntelliJ monitors the changes of the project content and uses the information from those caches to generate the set of dirty and deleted files for every compilation. (Dirty files need to be recompiled, and deleted files need to have their output deleted). A builder can also report additional files as dirty (e.g. if a method is deleted, the builder can report the classes using this method as dirty.) A module-level builder can add some files to the dirty scope; if this happens, and if the builder returns
ADDITIONAL_PASS_REQUIRED from its build() method, another round of builder execution for the same module chunk will be started with the new dirty scope.
A builder may also want to have its custom caches to store additional information to support partial recompilation of a target (e.g. the dependencies between Java files in a module). To store this data, you can either store arbitrary files in the directory returned from
BuildDataManager.getDataPaths().getTargetDataRoot() or use a higher-level API:
To pass custom data between the invocation of the same builder between multiple targets, you can use
Services and extensions in External Builder