Add a converter for relative, null-able path fragments. RELNOTES: None. PiperOrigin-RevId: 238653219
diff --git a/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java b/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java index 8594a9c..4ce2631 100644 --- a/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java +++ b/src/main/java/com/google/devtools/build/lib/util/OptionsUtils.java
@@ -15,16 +15,16 @@ package com.google.devtools.build.lib.util; import com.google.common.base.Preconditions; +import com.google.common.base.StandardSystemProperty; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.common.options.Converter; +import com.google.devtools.common.options.OptionsParsingException; import com.google.devtools.common.options.OptionsParsingResult; import com.google.devtools.common.options.ParsedOptionDescription; import java.util.List; -/** - * Blaze-specific option utilities. - */ +/** Blaze-specific option utilities. */ public final class OptionsUtils { /** @@ -46,8 +46,8 @@ } /** - * Returns a string representation of the non-hidden explicitly or implicitly - * specified options; option values are shell-escaped. + * Returns a string representation of the non-hidden explicitly or implicitly specified options; + * option values are shell-escaped. */ public static String asShellEscapedString(OptionsParsingResult options) { return asShellEscapedString(options.asCompleteListOfParsedOptions()); @@ -86,27 +86,19 @@ } /** - * Returns a string representation of the non-hidden explicitly or implicitly - * specified options, filtering out any sensitive options; option values are - * shell-escaped. + * Returns a string representation of the non-hidden explicitly or implicitly specified options, + * filtering out any sensitive options; option values are shell-escaped. */ public static String asFilteredShellEscapedString(OptionsParsingResult options) { return asFilteredShellEscapedString(options, options.asCompleteListOfParsedOptions()); } - /** - * Converter from String to PathFragment. - */ - public static class PathFragmentConverter - implements Converter<PathFragment> { + /** Converter from String to PathFragment. */ + public static class PathFragmentConverter implements Converter<PathFragment> { @Override public PathFragment convert(String input) { - String path = Preconditions.checkNotNull(input); - if (!path.isEmpty() && path.startsWith("~/")) { - path = path.replace("~", System.getProperty("user.home")); - } - return PathFragment.create(path); + return convertOptionsPathFragment(Preconditions.checkNotNull(input)); } @Override @@ -115,6 +107,30 @@ } } + /** Converter from String to PathFragment. If the input is empty returns {@code null} instead. */ + public static class EmptyToNullRelativePathFragmentConverter implements Converter<PathFragment> { + + @Override + public PathFragment convert(String input) throws OptionsParsingException { + if (input.isEmpty()) { + return null; + } + + PathFragment pathFragment = convertOptionsPathFragment(input); + + if (pathFragment.isAbsolute()) { + throw new OptionsParsingException("Expected relative path but got '" + input + "'."); + } + + return pathFragment; + } + + @Override + public String getTypeDescription() { + return "a relative path"; + } + } + /** Converts from a colon-separated list of strings into a list of PathFragment instances. */ public static class PathFragmentListConverter implements Converter<ImmutableList<PathFragment>> { @@ -123,10 +139,7 @@ ImmutableList.Builder<PathFragment> result = ImmutableList.builder(); for (String piece : input.split(":")) { if (!piece.isEmpty()) { - if (piece.startsWith("~/")) { - piece = piece.replace("~", System.getProperty("user.home")); - } - result.add(PathFragment.create(piece)); + result.add(convertOptionsPathFragment(piece)); } } return result.build(); @@ -137,4 +150,11 @@ return "a colon-separated list of paths"; } } + + private static PathFragment convertOptionsPathFragment(String path) { + if (!path.isEmpty() && path.startsWith("~/")) { + path = path.replace("~", StandardSystemProperty.USER_HOME.value()); + } + return PathFragment.create(path); + } }
diff --git a/src/test/java/com/google/devtools/build/lib/util/OptionsUtilsTest.java b/src/test/java/com/google/devtools/build/lib/util/OptionsUtilsTest.java index fb69921..24a2b25 100644 --- a/src/test/java/com/google/devtools/build/lib/util/OptionsUtilsTest.java +++ b/src/test/java/com/google/devtools/build/lib/util/OptionsUtilsTest.java
@@ -14,6 +14,7 @@ package com.google.devtools.build.lib.util; import static com.google.common.truth.Truth.assertThat; +import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows; import com.google.devtools.build.lib.util.OptionsUtils.PathFragmentConverter; import com.google.devtools.build.lib.util.OptionsUtils.PathFragmentListConverter; @@ -25,58 +26,52 @@ import com.google.devtools.common.options.OptionPriority.PriorityCategory; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsParser; +import com.google.devtools.common.options.OptionsParsingException; import java.util.Arrays; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Test for {@link OptionsUtils}. - */ +/** Test for {@link OptionsUtils}. */ @RunWith(JUnit4.class) public class OptionsUtilsTest { public static class IntrospectionExample extends OptionsBase { @Option( - name = "alpha", - documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, - effectTags = {OptionEffectTag.NO_OP}, - defaultValue = "alpha" - ) + name = "alpha", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.NO_OP}, + defaultValue = "alpha") public String alpha; @Option( - name = "beta", - documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, - effectTags = {OptionEffectTag.NO_OP}, - defaultValue = "beta" - ) + name = "beta", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.NO_OP}, + defaultValue = "beta") public String beta; @Option( - name = "gamma", - documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, - effectTags = {OptionEffectTag.NO_OP}, - defaultValue = "gamma" - ) + name = "gamma", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.NO_OP}, + defaultValue = "gamma") public String gamma; @Option( - name = "delta", - documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, - effectTags = {OptionEffectTag.NO_OP}, - defaultValue = "delta" - ) + name = "delta", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.NO_OP}, + defaultValue = "delta") public String delta; @Option( - name = "echo", - metadataTags = {OptionMetadataTag.HIDDEN}, - documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, - effectTags = {OptionEffectTag.NO_OP}, - defaultValue = "echo" - ) + name = "echo", + metadataTags = {OptionMetadataTag.HIDDEN}, + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.NO_OP}, + defaultValue = "echo") public String echo; } @@ -103,19 +98,17 @@ public static class BooleanOpts extends OptionsBase { @Option( - name = "b_one", - documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, - effectTags = {OptionEffectTag.NO_OP}, - defaultValue = "true" - ) + name = "b_one", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.NO_OP}, + defaultValue = "true") public boolean bOne; @Option( - name = "b_two", - documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, - effectTags = {OptionEffectTag.NO_OP}, - defaultValue = "false" - ) + name = "b_two", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.NO_OP}, + defaultValue = "false") public boolean bTwo; } @@ -194,4 +187,25 @@ assertThat(convertOne("foo/bar/baz")).isEqualTo(fragment("foo/bar/baz")); assertThat(convertOne("~/foo")).isEqualTo(fragment(System.getProperty("user.home") + "/foo")); } + + @Test + public void emptyPathFragmentToNull() throws Exception { + assertThat(new OptionsUtils.EmptyToNullRelativePathFragmentConverter().convert("")).isNull(); + } + + @Test + public void absolutePathFragmentThrows() throws Exception { + OptionsParsingException exception = + assertThrows( + OptionsParsingException.class, + () -> new OptionsUtils.EmptyToNullRelativePathFragmentConverter().convert("/abs")); + + assertThat(exception).hasMessageThat().contains("/abs"); + } + + @Test + public void relativePathFragment() throws Exception { + assertThat(new OptionsUtils.EmptyToNullRelativePathFragmentConverter().convert("path/to/me")) + .isEqualTo(PathFragment.create("path/to/me")); + } }