/*
* Copyright 2016-present Facebook, Inc.
*
* 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 com.facebook.buck.intellij.ideabuck.autodeps;
import com.google.common.annotations.VisibleForTesting;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter; // NOPMD not in core Buck
import java.io.UnsupportedEncodingException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class BuckDeps {
private static final Logger LOG = Logger.getInstance(BuckDeps.class);
// Patterns to search within a BUCK file for known rule
private static final Pattern autodepsPattern = Pattern.compile("autodeps\\s*=\\s*True");
private static final Pattern depsPattern = Pattern.compile("deps\\s*=\\s*\\[([^\\]]+)\\]");
private static final Pattern exportedDepsPattern =
Pattern.compile("exported_deps\\s*=\\s*\\[([^\\]]+)\\]");
private static final Pattern visibilityPattern =
Pattern.compile("visibility\\s*=\\s*\\[([^\\]]+)\\]");
private BuckDeps() {}
public static void addDeps(
Project project,
String importBuckPath,
String currentBuckPath,
String importTarget,
String currentTarget) {
String buckPath = project.getBaseDir().getPath() + "/" + currentBuckPath + "/BUCK";
String contents = readBuckFile(buckPath);
String dependency = "//" + importBuckPath + ":" + importTarget;
String newContents = maybeAddDepToTarget(contents, dependency, currentTarget);
if (!contents.equals(newContents)) {
writeBuckFile(buckPath, newContents);
}
}
public static void addVisibility(
Project project,
String importBuckPath,
String currentBuckPath,
String importTarget,
String currentTarget) {
String buckPath = project.getBaseDir().getPath() + "/" + importBuckPath + "/BUCK";
String contents = readBuckFile(buckPath);
String visibility = "//" + currentBuckPath + ":" + currentTarget;
String newContents = maybeAddVisibilityToTarget(contents, visibility, importTarget);
if (!contents.equals(newContents)) {
writeBuckFile(buckPath, newContents);
}
}
/**
* Given the contents of a buckfile and a target name, returns an array of indices into the
* content [start, end] of a guess where that target def in the given BUCK file.
*/
@VisibleForTesting
static int[] findTargetInBuckFileContents(String contents, String target) {
int nameOffset = contents.indexOf("'" + target + "'");
if (nameOffset == -1) {
return null; //target not found
}
// March backwards to figure out where the rule starts
int targetStart = nameOffset;
int depth = 1;
while (depth > 0) {
targetStart -= 1;
if (targetStart <= 0) {
return null; // marched back to beginning of file...this ain't it.
}
char ch = contents.charAt(targetStart);
if (ch == '(') {
depth -= 1;
} else if (ch == ')') {
depth += 1;
}
}
// March forwards to figure out where the rule ends
int targetEnd = nameOffset + target.length();
depth = 1;
while (depth > 0) {
if (targetEnd >= contents.length()) {
return null; // marched past end of file...didn't find it.
}
char ch = contents.charAt(targetEnd);
if (ch == '(') {
depth += 1;
} else if (ch == ')') {
depth -= 1;
}
targetEnd += 1;
}
return new int[] {targetStart, targetEnd};
}
/**
* Given the contents of a BUCK file, try to modify the contents to add the given dependency to
* the given target.
*/
@VisibleForTesting
static String maybeAddDepToTarget(String buckContents, String dependency, String target) {
int[] targetOffset = findTargetInBuckFileContents(buckContents, target);
if (targetOffset == null) {
LOG.warn("Couldn't find target definition for " + target);
// TODO(ideabuck): make this a better parser
return buckContents;
}
String targetDef = buckContents.substring(targetOffset[0], targetOffset[1]);
if (autodepsPattern.matcher(targetDef).find()) {
// TODO(ideabuck): Once 'buck autodeps' stabilizes, should we invoke it here?
return buckContents;
}
Matcher exportedDepsMatcher = exportedDepsPattern.matcher(targetDef);
if (exportedDepsMatcher.find()) {
String exportedDeps = exportedDepsMatcher.group(1);
if (exportedDeps.contains(dependency)) {
// If it already appears in the exported_deps section, nothing to do
return buckContents;
}
}
Matcher depsMatcher = depsPattern.matcher(targetDef);
if (!depsMatcher.find()) {
LOG.warn(
"Couldn't figure out where to add new dependency on "
+ dependency
+ " in definition for "
+ target);
return buckContents;
}
if (depsMatcher.group(1).contains(dependency)) {
// already have dep in the deps section
return buckContents;
}
int offset = targetOffset[0] + depsMatcher.start(1);
return buckContents.substring(0, offset)
+ "\n\t\t'"
+ dependency
+ "',"
+ buckContents.substring(offset);
}
@VisibleForTesting
static String maybeAddVisibilityToTarget(String buckContents, String visibility, String target) {
int[] targetOffset = findTargetInBuckFileContents(buckContents, target);
if (targetOffset == null) {
LOG.warn("Couldn't find target definition for " + target);
// TODO(ideabuck): make this a better parser
return buckContents;
}
String targetDef = buckContents.substring(targetOffset[0], targetOffset[1]);
Matcher visibilityMatcher = visibilityPattern.matcher(targetDef);
if (!visibilityMatcher.find()) {
LOG.warn(
"Couldn't figure out where to increase visibility for "
+ target
+ " to include "
+ visibility);
// TODO(ideabuck): try harder to figure out where to add the visibility
return buckContents;
}
if (visibilityMatcher.group(1).contains(visibility)) {
return buckContents; // already visibile to this caller
}
int offset = targetOffset[0] + visibilityMatcher.start(1);
buckContents =
buckContents.substring(0, offset)
+ "\n\t\t'"
+ visibility
+ "',"
+ buckContents.substring(offset);
return buckContents;
}
private static String readBuckFile(String buckFilePath) {
String str = "";
File file = new File(buckFilePath);
try (FileInputStream fis = new FileInputStream(file)) {
byte[] data = new byte[(int) file.length()];
fis.read(data);
fis.close();
str = new String(data, "UTF-8");
} catch (FileNotFoundException e) {
LOG.error(e.toString());
} catch (UnsupportedEncodingException e) {
LOG.error(e.toString());
} catch (IOException e) {
LOG.error(e.toString());
}
return str;
}
private static void writeBuckFile(String buckFilePath, String text) {
try (PrintWriter out = new PrintWriter(buckFilePath)) { // NOPMD not in core Buck
out.write(text);
} catch (FileNotFoundException e) {
LOG.error(e.toString());
} catch (IOException e) {
LOG.error(e.toString());
}
}
}