2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.compiler
.actions
;
18 import com
.intellij
.compiler
.CompilerConfiguration
;
19 import com
.intellij
.compiler
.CompilerConfigurationImpl
;
20 import com
.intellij
.compiler
.ant
.*;
21 import com
.intellij
.compiler
.impl
.CompilerUtil
;
22 import com
.intellij
.openapi
.actionSystem
.AnActionEvent
;
23 import com
.intellij
.openapi
.actionSystem
.DataContext
;
24 import com
.intellij
.openapi
.actionSystem
.PlatformDataKeys
;
25 import com
.intellij
.openapi
.actionSystem
.Presentation
;
26 import com
.intellij
.openapi
.application
.ApplicationManager
;
27 import com
.intellij
.openapi
.compiler
.CompilerBundle
;
28 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
29 import com
.intellij
.openapi
.progress
.ProgressManager
;
30 import com
.intellij
.openapi
.progress
.Task
;
31 import com
.intellij
.openapi
.project
.Project
;
32 import com
.intellij
.openapi
.ui
.Messages
;
33 import com
.intellij
.openapi
.util
.io
.FileUtil
;
34 import com
.intellij
.openapi
.vfs
.LocalFileSystem
;
35 import com
.intellij
.openapi
.vfs
.ReadonlyStatusHandler
;
36 import com
.intellij
.openapi
.vfs
.VfsUtil
;
37 import com
.intellij
.openapi
.vfs
.VirtualFile
;
38 import org
.jetbrains
.annotations
.NonNls
;
39 import org
.jetbrains
.annotations
.NotNull
;
44 public class GenerateAntBuildAction
extends CompileActionBase
{
45 @NonNls private static final String XML_EXTENSION
= ".xml";
47 protected void doAction(DataContext dataContext
, final Project project
) {
48 ((CompilerConfigurationImpl
)CompilerConfiguration
.getInstance(project
)).convertPatterns();
49 final GenerateAntBuildDialog dialog
= new GenerateAntBuildDialog(project
);
52 final String
[] names
= dialog
.getRepresentativeModuleNames();
53 final GenerationOptionsImpl
[] genOptions
= {null};
54 Runnable runnable
= new Runnable() {
56 genOptions
[0] = new GenerationOptionsImpl(project
, dialog
.isGenerateSingleFileBuild(), dialog
.isFormsCompilationEnabled(),
57 dialog
.isBackupFiles(), dialog
.isForceTargetJdk(), dialog
.isRuntimeClasspathInlined(),
58 dialog
.isIdeaHomeGenerated(), names
);
61 if (!ProgressManager
.getInstance().runProcessWithProgressSynchronously(runnable
, "Analyzing project structure...", true, project
)) {
64 if (!validateGenOptions(project
, genOptions
[0])) {
67 generate(project
, genOptions
[0]);
72 * Validate generation options and notify user about possible problems
74 * @param project a context project
75 * @param genOptions a generation optiosn
76 * @return true if the generator should proceed with current options or if there is not conflict.
78 private static boolean validateGenOptions(Project project
, GenerationOptionsImpl genOptions
) {
79 final Collection
<String
> EMPTY
= Collections
.emptyList();
80 Collection
<String
> conflicts
= EMPTY
;
81 for (ModuleChunk chunk
: genOptions
.getModuleChunks()) {
82 final ChunkCustomCompilerExtension
[] customeCompilers
= chunk
.getCustomCompilers();
83 if (customeCompilers
.length
> 1) {
84 if (conflicts
== EMPTY
) {
85 conflicts
= new LinkedList
<String
>();
87 conflicts
.add(chunk
.getName());
90 if (!conflicts
.isEmpty()) {
91 StringBuilder msg
= new StringBuilder();
92 for (String conflictingChunk
: conflicts
) {
93 msg
.append(CompilerBundle
.message("generate.ant.build.custom.compiler.conflict.message.row", conflictingChunk
));
96 .showOkCancelDialog(project
, CompilerBundle
.message("generate.ant.build.custom.compiler.conflict.message", msg
.toString()),
97 CompilerBundle
.message("generate.ant.build.custom.compiler.conflict.titile"), Messages
.getErrorIcon());
105 public void update(AnActionEvent event
) {
106 Presentation presentation
= event
.getPresentation();
107 Project project
= PlatformDataKeys
.PROJECT
.getData(event
.getDataContext());
108 presentation
.setEnabled(project
!= null);
111 private void generate(final Project project
, final GenerationOptions genOptions
) {
112 ApplicationManager
.getApplication().saveAll();
113 final List
<File
> filesToRefresh
= new ArrayList
<File
>();
114 final IOException
[] _ex
= new IOException
[]{null};
115 final List
<File
> _generated
= new ArrayList
<File
>();
118 if (genOptions
.generateSingleFile
) {
119 final File projectBuildFileDestDir
= VfsUtil
.virtualToIoFile(project
.getBaseDir());
120 final File destFile
= new File(projectBuildFileDestDir
, BuildProperties
.getProjectBuildFileName(project
) + XML_EXTENSION
);
121 final File propertiesFile
= new File(projectBuildFileDestDir
, BuildProperties
.getPropertyFileName(project
));
123 ensureFilesWritable(project
, new File
[]{destFile
, propertiesFile
});
126 final List
<File
> allFiles
= new ArrayList
<File
>();
128 final File projectBuildFileDestDir
= VfsUtil
.virtualToIoFile(project
.getBaseDir());
129 allFiles
.add(new File(projectBuildFileDestDir
, BuildProperties
.getProjectBuildFileName(project
) + XML_EXTENSION
));
130 allFiles
.add(new File(projectBuildFileDestDir
, BuildProperties
.getPropertyFileName(project
)));
132 final ModuleChunk
[] chunks
= genOptions
.getModuleChunks();
133 for (final ModuleChunk chunk
: chunks
) {
134 final File chunkBaseDir
= BuildProperties
.getModuleChunkBaseDir(chunk
);
135 allFiles
.add(new File(chunkBaseDir
, BuildProperties
.getModuleChunkBuildFileName(chunk
) + XML_EXTENSION
));
138 ensureFilesWritable(project
, allFiles
.toArray(new File
[allFiles
.size()]));
141 new Task
.Modal(project
, CompilerBundle
.message("generate.ant.build.title"), false) {
142 public void run(@NotNull final ProgressIndicator indicator
) {
143 indicator
.setIndeterminate(true);
144 indicator
.setText(CompilerBundle
.message("generate.ant.build.progress.message"));
146 final File
[] generated
;
147 if (genOptions
.generateSingleFile
) {
148 generated
= generateSingleFileBuild(project
, genOptions
, filesToRefresh
);
151 generated
= generateMultipleFileBuild(project
, genOptions
, filesToRefresh
);
153 if (generated
!= null) {
154 _generated
.addAll(Arrays
.asList(generated
));
157 catch (IOException e
) {
164 catch (IOException e
) {
168 if (_ex
[0] != null) {
169 Messages
.showErrorDialog(project
, CompilerBundle
.message("error.ant.files.generate.failed", _ex
[0].getMessage()),
170 CompilerBundle
.message("generate.ant.build.title"));
173 StringBuffer filesString
= new StringBuffer();
174 for (int idx
= 0; idx
< _generated
.size(); idx
++) {
175 final File file
= _generated
.get(idx
);
177 filesString
.append(",\n");
179 filesString
.append(file
.getPath());
181 Messages
.showInfoMessage(project
, CompilerBundle
.message("message.ant.files.generated.ok", filesString
.toString()),
182 CompilerBundle
.message("generate.ant.build.title"));
185 if (filesToRefresh
.size() > 0) {
186 CompilerUtil
.refreshIOFiles(filesToRefresh
);
190 private boolean backup(final File file
, final Project project
, GenerationOptions genOptions
, List
<File
> filesToRefresh
) {
191 if (!genOptions
.backupPreviouslyGeneratedFiles
|| !file
.exists()) {
194 final String path
= file
.getPath();
195 final int extensionIndex
= path
.lastIndexOf(".");
196 final String extension
= path
.substring(extensionIndex
, path
.length());
197 //noinspection HardCodedStringLiteral
198 final String backupPath
= path
.substring(0, extensionIndex
) +
200 new Date(file
.lastModified()).toString().replaceAll("\\s+", "_").replaceAll(":", "-") +
202 final File backupFile
= new File(backupPath
);
205 FileUtil
.rename(file
, backupFile
);
208 catch (IOException e
) {
209 Messages
.showErrorDialog(project
, CompilerBundle
.message("error.ant.files.backup.failed", path
),
210 CompilerBundle
.message("generate.ant.build.title"));
213 filesToRefresh
.add(backupFile
);
217 private File
[] generateSingleFileBuild(Project project
, GenerationOptions genOptions
, List
<File
> filesToRefresh
) throws IOException
{
218 final File projectBuildFileDestDir
= VfsUtil
.virtualToIoFile(project
.getBaseDir());
219 projectBuildFileDestDir
.mkdirs();
220 final File destFile
= new File(projectBuildFileDestDir
, BuildProperties
.getProjectBuildFileName(project
) + XML_EXTENSION
);
221 final File propertiesFile
= new File(projectBuildFileDestDir
, BuildProperties
.getPropertyFileName(project
));
223 if (!backup(destFile
, project
, genOptions
, filesToRefresh
)) {
226 if (!backup(propertiesFile
, project
, genOptions
, filesToRefresh
)) {
230 generateSingleFileBuild(project
, genOptions
, destFile
, propertiesFile
);
232 filesToRefresh
.add(destFile
);
233 filesToRefresh
.add(propertiesFile
);
234 return new File
[]{destFile
, propertiesFile
};
237 public static void generateSingleFileBuild(final Project project
,
238 final GenerationOptions genOptions
,
239 final File buildxmlFile
,
240 final File propertiesFile
) throws IOException
{
241 FileUtil
.createIfDoesntExist(buildxmlFile
);
242 FileUtil
.createIfDoesntExist(propertiesFile
);
243 final PrintWriter dataOutput
= makeWriter(buildxmlFile
);
245 new SingleFileProjectBuild(project
, genOptions
).generate(dataOutput
);
250 final PrintWriter propertiesOut
= makeWriter(propertiesFile
);
252 new PropertyFileGeneratorImpl(project
, genOptions
).generate(propertiesOut
);
255 propertiesOut
.close();
260 * Create print writer over file with UTF-8 encoding
262 * @param buildxmlFile a file to write to
263 * @return a created print writer
264 * @throws UnsupportedEncodingException if endcoding not found
265 * @throws FileNotFoundException if file not found
267 private static PrintWriter
makeWriter(final File buildxmlFile
) throws UnsupportedEncodingException
, FileNotFoundException
{
268 return new PrintWriter(new OutputStreamWriter(new FileOutputStream(buildxmlFile
), "UTF-8"));
271 private void ensureFilesWritable(Project project
, File
[] files
) throws IOException
{
272 final List
<VirtualFile
> toCheck
= new ArrayList
<VirtualFile
>(files
.length
);
273 final LocalFileSystem lfs
= LocalFileSystem
.getInstance();
274 for (File file
: files
) {
275 final VirtualFile vFile
= lfs
.findFileByIoFile(file
);
280 final ReadonlyStatusHandler
.OperationStatus status
=
281 ReadonlyStatusHandler
.getInstance(project
).ensureFilesWritable(VfsUtil
.toVirtualFileArray(toCheck
));
282 if (status
.hasReadonlyFiles()) {
283 throw new IOException(status
.getReadonlyFilesMessage());
287 public File
[] generateMultipleFileBuild(Project project
, GenerationOptions genOptions
, List
<File
> filesToRefresh
) throws IOException
{
288 final File projectBuildFileDestDir
= VfsUtil
.virtualToIoFile(project
.getBaseDir());
289 projectBuildFileDestDir
.mkdirs();
290 final List
<File
> generated
= new ArrayList
<File
>();
291 final File projectBuildFile
= new File(projectBuildFileDestDir
, BuildProperties
.getProjectBuildFileName(project
) + XML_EXTENSION
);
292 final File propertiesFile
= new File(projectBuildFileDestDir
, BuildProperties
.getPropertyFileName(project
));
293 final ModuleChunk
[] chunks
= genOptions
.getModuleChunks();
295 final File
[] chunkFiles
= new File
[chunks
.length
];
296 for (int idx
= 0; idx
< chunks
.length
; idx
++) {
297 final ModuleChunk chunk
= chunks
[idx
];
298 final File chunkBaseDir
= BuildProperties
.getModuleChunkBaseDir(chunk
);
299 chunkFiles
[idx
] = new File(chunkBaseDir
, BuildProperties
.getModuleChunkBuildFileName(chunk
) + XML_EXTENSION
);
302 if (!backup(projectBuildFile
, project
, genOptions
, filesToRefresh
)) {
305 if (!backup(propertiesFile
, project
, genOptions
, filesToRefresh
)) {
309 FileUtil
.createIfDoesntExist(projectBuildFile
);
310 final PrintWriter mainDataOutput
= makeWriter(projectBuildFile
);
312 final MultipleFileProjectBuild build
= new MultipleFileProjectBuild(project
, genOptions
);
313 build
.generate(mainDataOutput
);
314 generated
.add(projectBuildFile
);
316 // the sequence in which modules are imported is important cause output path properties for dependent modules should be defined first
318 for (int idx
= 0; idx
< chunks
.length
; idx
++) {
319 final ModuleChunk chunk
= chunks
[idx
];
320 final File chunkBuildFile
= chunkFiles
[idx
];
321 final File chunkBaseDir
= chunkBuildFile
.getParentFile();
322 if (chunkBaseDir
!= null) {
323 chunkBaseDir
.mkdirs();
325 final boolean moduleBackupOk
= backup(chunkBuildFile
, project
, genOptions
, filesToRefresh
);
326 if (!moduleBackupOk
) {
330 FileUtil
.createIfDoesntExist(chunkBuildFile
);
331 final PrintWriter out
= makeWriter(chunkBuildFile
);
333 new ModuleChunkAntProject(project
, chunk
, genOptions
).generate(out
);
334 generated
.add(chunkBuildFile
);
342 mainDataOutput
.close();
345 final PrintWriter propertiesOut
= makeWriter(propertiesFile
);
347 new PropertyFileGeneratorImpl(project
, genOptions
).generate(propertiesOut
);
348 generated
.add(propertiesFile
);
351 propertiesOut
.close();
354 filesToRefresh
.addAll(generated
);
355 return generated
.toArray(new File
[generated
.size()]);