Esempio n. 1
0
 def test_flatten_shlexed_list(self) -> None:
     assert flatten_shlexed_list(["arg1", "arg2"]) == ["arg1", "arg2"]
     assert flatten_shlexed_list(["arg1 arg2"]) == ["arg1", "arg2"]
     assert flatten_shlexed_list(["arg1 arg2=foo", "--arg3"
                                  ]) == ["arg1", "arg2=foo", "--arg3"]
     assert flatten_shlexed_list(["arg1='foo bar'", "arg2='baz'"
                                  ]) == ["arg1=foo bar", "arg2=baz"]
Esempio n. 2
0
 def __init__(
     self,
     *,
     source_level,
     target_level,
     args,
     jvm_options,
     strict=False,
     name=None,
     by_default=False,
 ):
     """
 :param source_level: Revision object or string for the java source level.
 :param target_level: Revision object or string for the java target level.
 :param list args: Additional arguments to pass to the java compiler.
 :param list jvm_options: Additional jvm options specific to this JVM platform.
 :param boolean strict: Whether to use the target level as a lower bound or an exact requirement.
 :param str name: name to identify this platform.
 :param by_default: True if this value was inferred by omission of a specific platform setting.
 """
     self.source_level = JvmPlatform.parse_java_version(source_level)
     self.target_level = JvmPlatform.parse_java_version(target_level)
     self.args = tuple(flatten_shlexed_list(args or ()))
     self.jvm_options = tuple(flatten_shlexed_list(jvm_options or ()))
     self.strict = strict
     self.name = name
     self._by_default = by_default
     self._validate_source_target()
Esempio n. 3
0
 def process_list(lst):
     lst = [
         self._convert_member_type(member_type, val) for val in lst
     ]
     if member_type == shell_str:
         lst = flatten_shlexed_list(lst)
     return lst
Esempio n. 4
0
    def _compute_value(self, dest, kwargs, flag_val_strs):
        """Compute the value to use for an option.

        The source of the default value is chosen according to the ranking in RankedValue.
        """

        # Helper function to convert a string to a value of the option's type.
        def to_value_type(val_str):
            if val_str is None:
                return None
            if kwargs.get("type") == bool:
                return self._ensure_bool(val_str)
            type_arg = kwargs.get("type", str)
            try:
                return self._wrap_type(type_arg)(val_str)
            except (TypeError, ValueError) as e:
                raise ParseError(
                    f"Error applying type '{type_arg.__name__}' to option value '{val_str}', for option "
                    f"'--{dest}' in {self._scope_str()}: {e}")

        # Helper function to expand a fromfile=True value string, if needed.
        # May return a string or a dict/list decoded from a json/yaml file.
        def expand(val_or_str):
            if (kwargs.get("fromfile", True) and isinstance(val_or_str, str)
                    and val_or_str.startswith("@")):
                if val_or_str.startswith(
                        "@@"
                ):  # Support a literal @ for fromfile values via @@.
                    return val_or_str[1:]
                else:
                    fromfile = val_or_str[1:]
                    try:
                        with open(fromfile, "r") as fp:
                            s = fp.read().strip()
                            if fromfile.endswith(".json"):
                                return json.loads(s)
                            elif fromfile.endswith(
                                    ".yml") or fromfile.endswith(".yaml"):
                                return yaml.safe_load(s)
                            else:
                                return s
                    except (IOError, ValueError, yaml.YAMLError) as e:
                        raise FromfileError(
                            f"Failed to read {dest} in {self._scope_str()} from file {fromfile}: {e!r}"
                        )
            else:
                return val_or_str

        # Get value from config files, and capture details about its derivation.
        config_details = None
        config_section = GLOBAL_SCOPE_CONFIG_SECTION if self._scope == GLOBAL_SCOPE else self._scope
        config_default_val_or_str = expand(
            self._config.get(Config.DEFAULT_SECTION, dest, default=None))
        config_val_or_str = expand(
            self._config.get(config_section, dest, default=None))
        config_source_file = self._config.get_source_for_option(
            config_section, dest) or self._config.get_source_for_option(
                Config.DEFAULT_SECTION, dest)
        if config_source_file is not None:
            config_source_file = os.path.relpath(config_source_file)
            config_details = f"in {config_source_file}"

        # Get value from environment, and capture details about its derivation.
        udest = dest.upper()
        if self._scope == GLOBAL_SCOPE:
            # For convenience, we allow three forms of env var for global scope options.
            # The fully-specified env var is PANTS_GLOBAL_FOO, which is uniform with PANTS_<SCOPE>_FOO
            # for all the other scopes.  However we also allow simply PANTS_FOO. And if the option name
            # itself starts with 'pants-' then we also allow simply FOO. E.g., PANTS_WORKDIR instead of
            # PANTS_PANTS_WORKDIR or PANTS_GLOBAL_PANTS_WORKDIR. We take the first specified value we
            # find, in this order: PANTS_GLOBAL_FOO, PANTS_FOO, FOO.
            env_vars = [f"PANTS_GLOBAL_{udest}", f"PANTS_{udest}"]
            if udest.startswith("PANTS_"):
                env_vars.append(udest)
        else:
            sanitized_env_var_scope = self._ENV_SANITIZER_RE.sub(
                "_", self._scope.upper())
            env_vars = [f"PANTS_{sanitized_env_var_scope}_{udest}"]

        env_val_or_str = None
        env_details = None
        if self._env:
            for env_var in env_vars:
                if env_var in self._env:
                    env_val_or_str = expand(self._env.get(env_var))
                    env_details = f"from env var {env_var}"
                    break

        # Get value from cmd-line flags.
        flag_vals = [to_value_type(expand(x)) for x in flag_val_strs]
        if is_list_option(kwargs):
            # Note: It's important to set flag_val to None if no flags were specified, so we can
            # distinguish between no flags set vs. explicit setting of the value to [].
            flag_val = ListValueComponent.merge(
                flag_vals) if flag_vals else None
        elif is_dict_option(kwargs):
            # Note: It's important to set flag_val to None if no flags were specified, so we can
            # distinguish between no flags set vs. explicit setting of the value to {}.
            flag_val = DictValueComponent.merge(
                flag_vals) if flag_vals else None
        elif len(flag_vals) > 1:
            raise ParseError(
                "Multiple cmd line flags specified for option {} in {}".format(
                    dest, self._scope_str()))
        elif len(flag_vals) == 1:
            flag_val = flag_vals[0]
        else:
            flag_val = None

        # Rank all available values.
        # Note that some of these values may already be of the value type, but type conversion
        # is idempotent, so this is OK.

        values_to_rank = [
            to_value_type(x) for x in [
                flag_val,
                env_val_or_str,
                config_val_or_str,
                config_default_val_or_str,
                kwargs.get("default"),
                None,
            ]
        ]
        # Note that ranked_vals will always have at least one element, and all elements will be
        # instances of RankedValue (so none will be None, although they may wrap a None value).
        ranked_vals = list(
            reversed(list(RankedValue.prioritized_iter(*values_to_rank))))

        def record_option(value, rank, option_details=None):
            deprecation_version = kwargs.get("removal_version")
            self._option_tracker.record_option(
                scope=self._scope,
                option=dest,
                value=value,
                rank=rank,
                deprecation_version=deprecation_version,
                details=option_details,
            )

        # Record info about the derivation of each of the contributing values.
        detail_history = []
        for ranked_val in ranked_vals:
            if ranked_val.rank in (RankedValue.CONFIG,
                                   RankedValue.CONFIG_DEFAULT):
                details = config_details
            elif ranked_val.rank == RankedValue.ENVIRONMENT:
                details = env_details
            else:
                details = None
            if details:
                detail_history.append(details)
            record_option(value=ranked_val.value,
                          rank=ranked_val.rank,
                          option_details=details)

        # Helper function to check various validity constraints on final option values.
        def check(val):
            if val is None:
                return
            choices = kwargs.get("choices")
            type_arg = kwargs.get("type")
            if choices is None and "type" in kwargs:
                if inspect.isclass(type_arg) and issubclass(type_arg, Enum):
                    choices = list(type_arg)
            # TODO: convert this into an enum() pattern match!
            if choices is not None and val not in choices:
                raise ParseError(
                    "`{}` is not an allowed value for option {} in {}. "
                    "Must be one of: {}".format(val, dest, self._scope_str(),
                                                choices))

            if type_arg == file_option:
                check_file_exists(val)
            if type_arg == dir_option:
                check_dir_exists(val)

        def check_file_exists(val) -> None:
            error_prefix = f"File value `{val}` for option `{dest}` in `{self._scope_str()}`"
            try:
                path = Path(val)
                path_with_buildroot = Path(get_buildroot(), val)
            except TypeError:
                raise ParseError(
                    f"{error_prefix} cannot be parsed as a file path.")
            if not path.is_file() and not path_with_buildroot.is_file():
                raise ParseError(f"{error_prefix} does not exist.")

        def check_dir_exists(val) -> None:
            error_prefix = f"Directory value `{val}` for option `{dest}` in `{self._scope_str()}`"
            try:
                path = Path(val)
                path_with_buildroot = Path(get_buildroot(), val)
            except TypeError:
                raise ParseError(
                    f"{error_prefix} cannot be parsed as a directory path.")
            if not path.is_dir() and not path_with_buildroot.is_dir():
                raise ParseError(f"{error_prefix} does not exist.")

        # Generate the final value from all available values, and check that it (or its members,
        # if a list) are in the set of allowed choices.
        if is_list_option(kwargs):
            merged_rank = ranked_vals[-1].rank
            merged_val = ListValueComponent.merge(
                [rv.value for rv in ranked_vals if rv.value is not None]).val
            # TODO: run `check()` for all elements of a list option too!!!
            merged_val = [
                self._convert_member_type(kwargs.get("member_type", str), x)
                for x in merged_val
            ]
            if kwargs.get("member_type") == shell_str:
                merged_val = flatten_shlexed_list(merged_val)
            for val in merged_val:
                check(val)
            ret = RankedValue(merged_rank, merged_val)
        elif is_dict_option(kwargs):
            # TODO: convert `member_type` for dict values too!
            merged_rank = ranked_vals[-1].rank
            merged_val = DictValueComponent.merge(
                [rv.value for rv in ranked_vals if rv.value is not None]).val
            for val in merged_val:
                check(val)
            ret = RankedValue(merged_rank, merged_val)
        else:
            ret = ranked_vals[-1]
            check(ret.value)

        # Record info about the derivation of the final value.
        merged_details = ", ".join(detail_history) if detail_history else None
        record_option(value=ret.value,
                      rank=ret.rank,
                      option_details=merged_details)

        # All done!
        return ret
Esempio n. 5
0
 def get_args(self) -> Tuple[str, ...]:
     return flatten_shlexed_list(self.get_options().args)