/*
* Copyright 2003-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.jps.build;
import com.intellij.openapi.util.io.FileUtil;
import jetbrains.mps.extapi.persistence.FileSystemBasedDataSource;
import jetbrains.mps.idea.core.make.MPSMakeConstants;
import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.tool.builder.make.ReducedMakeFacetConfiguration;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.jps.builders.logging.ProjectBuilderLogger;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.FSOperations;
import org.jetbrains.jps.incremental.ModuleBuildTarget;
import org.jetbrains.jps.incremental.ModuleLevelBuilder.OutputConsumer;
import org.jetbrains.jps.incremental.fs.CompilationRound;
import org.jetbrains.jps.incremental.java.JavaBuilder;
import org.jetbrains.jps.incremental.messages.BuildMessage.Kind;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import org.jetbrains.jps.util.JpsPathUtil;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.persistence.DataSource;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
public class MPSMakeFilesAfterProcessor {
@NonNls
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("jetbrains.mps.idea.core.MPSCoreBundle");
@NonNls
private static final String MPS_GENERATED_FILES = "MPS generated files:";
private final Map<SModel, ModuleBuildTarget> myModelToTargetMap;
private final GenerationPathsController myPathsController;
private final OutputConsumer myOutputConsumer;
private final CompileContext myContext;
public MPSMakeFilesAfterProcessor(Map<SModel, ModuleBuildTarget> modelToTargetMap, GenerationPathsController pathsController, OutputConsumer outputConsumer, CompileContext context) {
myModelToTargetMap = modelToTargetMap;
myPathsController = pathsController;
myOutputConsumer = outputConsumer;
myContext = context;
}
public boolean process(final ReducedMakeFacetConfiguration makeConfig) {
boolean success = processWrittenFiles(makeConfig);
success &= processDeletedFiles(makeConfig);
return success;
}
private ProjectBuilderLogger getBuilderLogger() {
return myContext.getLoggingManager().getProjectBuilderLogger();
}
private boolean processDeletedFiles(ReducedMakeFacetConfiguration makeConfig) {
boolean success = true;
List<String> deletedFiles = makeConfig.getDeletedFiles();
ProjectBuilderLogger logger = getBuilderLogger();
if (logger.isEnabled()) {
logger.logDeletedFiles(deletedFiles);
}
for (String deletedFile : deletedFiles) {
try {
FSOperations.markDeleted(myContext, new File(deletedFile));
} catch (IOException e) {
reportError(BUNDLE.getString("io.problem.while.deleting.files.with.fs"), e);
success = false;
}
}
return success;
}
private boolean processWrittenFiles(ReducedMakeFacetConfiguration makeConfig) {
Collection<String> writtenPaths = makeConfig.getWrittenFiles();
boolean success = true;
logGenerated(makeConfig);
for (String writtenPath : writtenPaths) {
SModel source = makeConfig.getSource(writtenPath);
ModuleBuildTarget target = myModelToTargetMap.get(source);
File writtenFile = new File(writtenPath);
try {
// all written java files need to be marked as dirty to get compiled by the JavaBuilder
FSOperations.markDirty(myContext, CompilationRound.CURRENT, new File(writtenPath));
myOutputConsumer.registerOutputFile(target, writtenFile, getFilesFromDataSource(source.getSource()));
} catch (IOException e) {
reportError(BUNDLE.getString("io.problem.while.marking.java.sources.dirty"), e);
success = false;
}
if (!isJava(writtenFile)) {
// all non-java files got to be copied (which are not in the caches folder, e.g. dependencies/generated)
if (!myPathsController.getRedirects().isInCacheOutput(writtenPath)) {
try {
copyResource(target, writtenFile);
} catch (IOException e) {
reportError(BUNDLE.getString("io.problem.during.resources.copying"), e);
success = false;
}
}
}
}
return success;
}
private void logGenerated(ReducedMakeFacetConfiguration makeFacetConfiguration) {
ProjectBuilderLogger logger = getBuilderLogger();
if (logger.isEnabled()) {
try {
logger.logCompiledPaths(makeFacetConfiguration.getWrittenFiles(), MPSMakeConstants.BUILDER_ID, MPS_GENERATED_FILES);
} catch (IOException ignored) {
}
}
}
// FIXME: FileSystemBasedDataSource#getAffectedFiles() needs to be rewritten in a way, where it gives no directories only files
private List<String> getFilesFromDataSource(DataSource dataSource) {
if (!(dataSource instanceof FileSystemBasedDataSource)) {
throw new IllegalArgumentException("MPS Idea plugin does not support the data source root formats other than FileDataSource and FilePerRootDataSource");
}
List<String> result = new ArrayList<String>();
Collection<IFile> affectedFiles = ((FileSystemBasedDataSource) dataSource).getAffectedFiles();
for (IFile file : affectedFiles) {
if (!file.isDirectory()) {
result.add(file.getPath());
} else {
for (IFile child : file.getChildren()) {
if (FileUtil.extensionEquals(child.getName(), MPSExtentions.MODEL_HEADER)
|| FileUtil.extensionEquals(child.getName(), MPSExtentions.MODEL_ROOT)) {
result.add(child.getPath());
}
}
}
}
return result;
}
private boolean isJava(File file) {
return JavaBuilder.JAVA_SOURCES_FILTER.accept(file);
}
private void copyResource(ModuleBuildTarget target, File file) throws IOException {
// the file is created in the output directory, which may have been redirected to a "hidden" location
File root = myPathsController.getOutputRoot(target);
String relativePath = FileUtil.getRelativePath(root, file);
if (relativePath == null) {
throw new IllegalStateException("File resource at " + file.getAbsolutePath() + " is not located under the root path " + root.getAbsolutePath());
}
relativePath = FileUtil.toSystemIndependentName(relativePath);
final String outputRootUrl = JpsJavaExtensionService.getInstance().getOutputUrl(target.getModule(), target.isTests());
final String targetPath = JpsPathUtil.urlToPath(outputRootUrl) + '/' + relativePath;
final File targetFile = new File(targetPath).getCanonicalFile();
FileUtil.copyContent(file, targetFile);
myOutputConsumer.registerOutputFile(target, targetFile, Collections.singletonList(file.getPath()));
}
private void reportError(String msg, Throwable e) {
myContext.processMessage(new CompilerMessage(msg, Kind.ERROR, e.getMessage()));
}
}