Fix wrong manipulation with ByteFragmentAtOffset
- the offset inside ByteFragmentAtOffset is the offset of the ByteBuffer from the beginning of the file, it is needed mostly for NinjaScope manipulations
- the offset inside ByteBufferFragment is the offset in the ByteBuffer, it points already at the place where lexing and parsing should begin
- add test for a big Ninja file and small ByteBuffer sizes used to read that file (the test is failing with the previous implementation)
Closes #10769.
PiperOrigin-RevId: 294685980
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/parser/NinjaParser.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/parser/NinjaParser.java
index 6517bf4..562f728 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/parser/NinjaParser.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/parser/NinjaParser.java
@@ -49,11 +49,7 @@
ByteBufferFragment fragment = byteFragmentAtOffset.getFragment();
int offset = byteFragmentAtOffset.getRealStartOffset();
- // Lexer should start at the beginning of byteFragmentAtOffset -> apply offset.
- ByteBufferFragment subFragment =
- fragment.subFragment(byteFragmentAtOffset.getOffset(), fragment.length());
- NinjaLexer lexer = new NinjaLexer(subFragment);
-
+ NinjaLexer lexer = new NinjaLexer(fragment);
if (!lexer.hasNextToken()) {
throw new IllegalStateException("Empty fragment passed as declaration.");
}
@@ -99,11 +95,21 @@
parseResult.addSubNinjaScope(declarationStart, subNinjaFuture);
break;
case BUILD:
- ByteFragmentAtOffset targetFragment =
- declarationStart == offset
- ? byteFragmentAtOffset
- // We pass an offset *inside the fragment* into ByteFragmentAtOffset constructor
- : new ByteFragmentAtOffset(declarationStart - fragment.getStartIncl(), fragment);
+ ByteFragmentAtOffset targetFragment;
+ if (declarationStart == offset) {
+ targetFragment = byteFragmentAtOffset;
+ } else {
+ // Method subFragment accepts only the offset *inside fragment*.
+ // So we should subtract the offset of fragment's buffer in file
+ // (byteFragmentAtOffset.getOffset()),
+ // and start of fragment inside buffer (fragment.getStartIncl()).
+ int fragmentStart =
+ declarationStart - byteFragmentAtOffset.getOffset() - fragment.getStartIncl();
+ targetFragment =
+ new ByteFragmentAtOffset(
+ byteFragmentAtOffset.getOffset(),
+ fragment.subFragment(fragmentStart, fragment.length()));
+ }
parseResult.addTarget(targetFragment);
break;
case DEFAULT:
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/pipeline/NinjaPipeline.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/pipeline/NinjaPipeline.java
index 98f8469..6463ea2 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/pipeline/NinjaPipeline.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/pipeline/NinjaPipeline.java
@@ -16,13 +16,13 @@
import static com.google.devtools.build.lib.concurrent.MoreFutures.waitForFutureAndGetWithCheckedException;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.devtools.build.lib.bazel.rules.ninja.file.ByteBufferFragment;
import com.google.devtools.build.lib.bazel.rules.ninja.file.ByteFragmentAtOffset;
import com.google.devtools.build.lib.bazel.rules.ninja.file.CollectingListFuture;
import com.google.devtools.build.lib.bazel.rules.ninja.file.GenericParsingException;
@@ -59,6 +59,7 @@
private final Collection<Path> includedOrSubninjaFiles;
private final String ownerTargetName;
private final Set<Path> childPaths;
+ private Integer readBlockSize;
/**
* @param basePath base path for resolving include and subninja paths.
@@ -116,15 +117,10 @@
for (ByteFragmentAtOffset byteFragmentAtOffset : targetFragments) {
future.add(
service.submit(
- () -> {
- // Lexer should start at the beginning of fragment -> apply offset.
- ByteBufferFragment bufferFragment = byteFragmentAtOffset.getFragment();
- ByteBufferFragment subFragment =
- bufferFragment.subFragment(
- byteFragmentAtOffset.getOffset(), bufferFragment.length());
- return new NinjaParserStep(new NinjaLexer(subFragment))
- .parseNinjaTarget(currentScope, byteFragmentAtOffset.getRealStartOffset());
- }));
+ () ->
+ new NinjaParserStep(new NinjaLexer(byteFragmentAtOffset.getFragment()))
+ .parseNinjaTarget(
+ currentScope, byteFragmentAtOffset.getRealStartOffset())));
}
queue.addAll(currentScope.getIncludedScopes());
queue.addAll(currentScope.getSubNinjaScopes());
@@ -171,6 +167,15 @@
}
}
+ /**
+ * Set the size of the block read by {@link ParallelFileProcessing}. Method is mainly intended to
+ * be used in tests.
+ */
+ @VisibleForTesting
+ public void setReadBlockSize(Integer readBlockSize) {
+ this.readBlockSize = readBlockSize;
+ }
+
private Path getChildNinjaPath(String rawText, String parentNinjaFileName)
throws GenericParsingException {
Path childPath = basePath.getRelative(rawText);
@@ -196,6 +201,9 @@
path.getBaseName()));
}
BlockParameters parameters = new BlockParameters(path.getFileSize());
+ if (readBlockSize != null) {
+ parameters.setReadBlockSize(readBlockSize);
+ }
return service.submit(
() -> {
try (ReadableByteChannel channel = path.createReadableByteChannel()) {
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaPipelineTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaPipelineTest.java
index 9198369..1ab3ed3 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaPipelineTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaPipelineTest.java
@@ -23,9 +23,13 @@
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.devtools.build.lib.bazel.rules.ninja.file.GenericParsingException;
+import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaRule;
+import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaRuleVariable;
import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaTarget;
+import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaVariableValue;
import com.google.devtools.build.lib.bazel.rules.ninja.pipeline.NinjaPipeline;
import com.google.devtools.build.lib.concurrent.ExecutorUtil;
+import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.DigestHashFunction.DefaultHashFunctionNotSetException;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
@@ -36,6 +40,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executors;
import org.junit.After;
import org.junit.Before;
@@ -257,6 +262,38 @@
+ "including 'one.ninja'.");
}
+ @Test
+ public void testBigFile() throws Exception {
+ String[] lines = new String[1000];
+ for (int i = 0; i < lines.length - 1; i++) {
+ lines[i] = "rule rule" + i + "\n command = echo 'Hello' > ${out}";
+ }
+ lines[999] = "build out: rule1";
+ Path path = tester.writeTmpFile("big_file.ninja", lines);
+ NinjaPipeline pipeline =
+ new NinjaPipeline(
+ path.getParentDirectory(), tester.getService(), ImmutableList.of(), "ninja_target");
+ // Test specifically that all manipulations with connecting buffers are working fine:
+ // for that, have relatively long file and small buffer size.
+ pipeline.setReadBlockSize(100);
+ List<NinjaTarget> targets = pipeline.pipeline(path);
+ assertThat(targets).hasSize(1);
+ Map<String, List<Pair<Integer, NinjaRule>>> rules = targets.get(0).getScope().getRules();
+ assertThat(rules).hasSize(999);
+ assertThat(rules.get("rule1")).hasSize(1);
+ NinjaVariableValue expectedValue =
+ NinjaVariableValue.builder().addText("echo 'Hello' > ").addVariable("out").build();
+ assertThat(
+ rules
+ .get("rule1")
+ .get(0)
+ .getSecond()
+ .getVariables()
+ .get(NinjaRuleVariable.COMMAND)
+ .getRawText())
+ .isEqualTo(expectedValue.getRawText());
+ }
+
private static void checkTargets(List<NinjaTarget> targets) {
assertThat(targets).hasSize(2);
for (NinjaTarget target : targets) {