Adding dynamically-generated file to artifact root

I understand that to add files to be packaged with an artifact in IDEA, one goes to "Project Structure", "Artifacts" and then "Add copy of".

What I'm looking for is a way to add to an artifact a file *that is only generated at build time*.

e.g., there is a another tool that creates a file, which I don't want to store anywhere, but only to include in the final artifact.

How can I do this programatically?

 

I can see a "Pre-processing" section that only lets us run Ant targets, which seems unhelpful since we're interested in just calling a library.

9 comments
Comment actions Permalink

What do you mean by "don't want to store anywhere"? If the tool creates a file it places it somewhere so you can just include it into the artifact configuration. Or you can create a simple Ant build file which calls the library and then puts the generated file into the artifact output directory. I see no other ways to do this in IDEA (though it's possible to write a plugin for IDEA which injects into artifact building process and do this).

0
Comment actions Permalink

Nikolay, I don't want to store the file in the source.

Ideally, the build process would trigger calling this tool which generates the file and add it to the compiled classes, where it would then be picked up for packaging.

Because the file doesn't exist when the artifact is defined, we can't add it in Output Layout. The only suitable option I can see is Pre-processing, but I'd really like to avoid adding temp Ant files to users' file systems. Constructing a file, saving it in the users' file system and then pointing the IDE to it sounds like an overly complex, time consuming and hackish approach for the given task.

Injecting into artifact building process sounds like the best approach here, but I couldn't find any useful classes. Would you mind pointing me to any useful resources to do this?

0
Comment actions Permalink

You need to implement a plugin which participates in external build process, and register implementation of org.jetbrains.jps.builders.artifacts.ArtifactBuildTaskProvider service in it (look at AntArtifactBuildTaskProvider for example).

0
Comment actions Permalink

We need to know what our source directory is at build time, in ArtifactBuildTaskProvider. How can we get the location of the webapp directory in ArtifactBuildTaskProvider?

I tried to search for it on the JpsArtifact argument from createArtifactBuildTasks(), but it's either buried very deep down or it's not there. The JpsPackagingElements returned by getRootElement() also don't provide a very browseable interface.

BuildTask.build() provides a CompileContext from where we can get a project's source roots. However, a project might have multiple source roots and we don't have any criteria to choose from at build time.

Ideally, we could just retrieve the root location of the project, or even the webapp directory location (although I'd rather not have the Web application dependency).

 

0
Comment actions Permalink

In order to get the location of 'webapp' directory you need to inspect 'getRootElement' of JpsArtifact. It represents the structure of the output layout tree. 'getChildren' method will return list of top-level nodes under '<output root>'. 'webapp directory contents' node corresponds to JpsDirectoryCopyPackagingElement and you can get the location of the directory by calling its 'getDirectoryPath' method. But in web artifacts this element isn't added to the children list directly. Instead it is wrapped into JpsJavaeeFacetResourcesPackagingElement which represents all web resources of a Web facet (because a facet may have more than one web root). If you don't want to have a dependency on 'JavaEE' plugin you can inspect all the children of the root element, try to find JpsDirectoryCopyPackagingElement among them, and if an element is instance of JpsComplexPackagingElement check its 'getSubstituion' elements instead. (I'll write javadoc for these classes.)

In general there is no such thing as the root location of the project. A project may have several modules and each module may have several content roots.

0
Comment actions Permalink

The reason I want to know where the user's source is located is to retrieve Git repository information.

I really want to avoid resorting to the modules to know where the user has their repository. It could be that they have multiple content root, each with its own Git repo, and that would leave the plugin unable to decide between which repo to use.

Ideally, we would use the project root directory, as in what's returned by Project.getBasePath(). That is the case we want to support. However, I can't find any way to get access to the project anywhere in ArtifactBuildTaskProvider or BuildTask. (I've tried using ProjectManager.getInstance() which isn't working due to NPE). Is it completely impossible to have access to the project there? I would expect the JpsArtifact to be bound to a project.

 

Also, can you shed some light into why jps-plugin needs to be its own module? We're running into a circular dependency situation where google-cloud-tools-plugin depends on jps-plugin and jps-plugin also depends on google-cloud-tools-plugin. It's uncertain that we might be able to factor some code out to another module, but that could cause module proliferation which is undesirable.

0
Comment actions Permalink

Besides my two questions above, it would also be great to know how I can have access to the project facets from a build context. I haven't found any way of getting a facet from JpsArtifact or CompileContext.

It's important to read the facet from the build because it's what determines if our step should actually happen, if errors should be ignored, etc.

0
Comment actions Permalink

Project.getBasePath returns the directory which contains .idea folder (or *.ipr file) with the project settings. This directory may not be related to the location of the project sources (some people store .idea directory in a separate directory, e.g. if they work in teams where different IDEs are used and don't want to pollute project sources with IDE-specific files).

ProjectManager (and many other classes) aren't available in JPS plugins. JPS plugins need to be included into separate modules because they may use only limited subset of IntelliJ APIs (located in jps-model.jar, jps-builders.jar, util.jar and few others). This is required because we want the build process to start very fast, and we also reuse JPS parts to implement IDEA Project Runner in TeamCity.

So there is no way to get instance of Project inside the build process, but you can inspect instance of JpsProject which also contains information about the project configuration. 'Facet' class is also cannot be directly accessed from a JPS plugin but you may read its settings from the project configuration files into JPS model, look at JpsAppEngineModelSerializerExtension for example.

0
Comment actions Permalink

Thanks for the response, Nikolay.

Taking your input, and to get on the same page let me summarize the issues we are seeing:

1) No access to new Stackdriver facet. As per your response, I assume we should implement a similar set of classes to persist the facet settings into a JpsModule.

2) Deciding which source root to select. I realise that when I create an empty artifact, the suggestions on the right-hand side menu include the modules and under each module, 'module' compile output. I'll probably have to grab the first source root for the selected module, though the code to achieve this seems to be very complicated.

3) compileServer.plugin classpath includes a jar with a dynamic version number. I read your gradle plugin documentation, and there only seems to be a way to script the IDEA version number, so sadly it looks like we'll have to change this value by hand every time we make a release of that jar.

4) Taking a similar route to what was done with the 'runtime' module, I extracted a piece of functionality from the Cloud Tools plugin to its own module, cloudsdk-service, in order to avoid a circular dependency (jps-plugin depends on google-cloud-tools-plugin and vice-versa). I'm seeing a NPE when I call CloudSdkService.getInstance();, because in the jps-plugin/cloudsdk-service context, ApplicationManager.getApplication() is returning null. It sounds like I'm missing a piece of configuration, but I wasn't able to discover which yet. Do you have any clue about what could cause this? FWIW, here is what we're currently doing.

0

Please sign in to leave a comment.