def test_invalid_field_name(self): with self.assertRaisesWithMessage( ValueError, "Type names and field names must be valid identifiers: '0isntanallowedfirstchar'" if PY3 else "Type names and field names cannot start with a number: '0isntanallowedfirstchar'"): datatype(['0isntanallowedfirstchar']) with self.assertRaisesWithMessage( ValueError, "Field names cannot start with an underscore: '_no_leading_underscore'"): datatype(['_no_leading_underscore'])
def test_subclasses_not_equal(self): foo = datatype(['val']) class Bar(foo): pass self.assertFalse(foo(1) == Bar(1)) self.assertTrue(foo(1) != Bar(1))
def test_double_passed_arg(self): bar = datatype(['val', 'zal']) with self.assertRaisesWithMessageContaining( TypeError, "__new__() got multiple values for {kw}argument 'val'" .format(kw='' if PY3 else 'keyword ')): bar(1, val=1)
def test_repr(self): bar = datatype(['val', 'zal'], superclass_name='Bar') self.assertEqual('Bar(val=1, zal=1)', repr(bar(1, 1))) class Foo(datatype(['val'], superclass_name='F'), AbsClass): pass self.assertEqual('Foo(val=1)', repr(Foo(1)))
def test_too_many_args(self): bar = datatype(['val', 'zal']) with self.assertRaisesWithMessageContaining( TypeError, '__new__() takes 3 positional arguments but 4 were given' if PY3 else '__new__() takes exactly 3 arguments (4 given)'): bar(1, 1, 1)
def of(cls, element_type, fields=('dependencies',)): type_name = b'{}({})'.format(cls.__name__, element_type.__name__) collection_of_type = type(type_name, (cls, datatype("{}s".format(element_type.__name__), fields)), {}) # Expose the custom class type at the module level to be pickle compatible. setattr(sys.modules[cls.__module__], type_name, collection_of_type) return collection_of_type
def test_repr(self): bar = datatype('Bar', ['val', 'zal']) self.assertEqual('Bar(val=1, zal=1)', repr(bar(1, 1))) class Foo(datatype('F', ['val']), AbsClass): pass # Maybe this should be 'Foo(val=1)'? self.assertEqual('F(val=1)', repr(Foo(1)))
def of(cls, *element_types): union = '|'.join(element_type.__name__ for element_type in element_types) type_name = b'{}.of({})'.format(cls.__name__, union) supertypes = (cls, datatype('Collection', ['dependencies'])) properties = {'element_types': element_types} collection_of_type = type(type_name, supertypes, properties) # Expose the custom class type at the module level to be pickle compatible. setattr(sys.modules[cls.__module__], type_name, collection_of_type) return collection_of_type
def of(cls, *element_types): union = '|'.join(element_type.__name__ for element_type in element_types) type_name = '{}.of({})'.format(cls.__name__, union) if PY2: type_name = type_name.encode('utf-8') type_checked_collection_class = datatype([ # Create a datatype with a single field 'dependencies' which is type-checked on construction # to be a collection containing elements of only the exact `element_types` specified. ('dependencies', TypedCollection(Exactly(*element_types))) ], superclass_name=cls.__name__) supertypes = (cls, type_checked_collection_class) properties = {'element_types': element_types} collection_of_type = type(type_name, supertypes, properties) # Expose the custom class type at the module level to be pickle compatible. setattr(sys.modules[cls.__module__], type_name, collection_of_type) return collection_of_type
def test_not_iterable(self): bar = datatype(['val']) with self.assertRaisesWithMessageContaining(TypeError, 'datatype object is not iterable'): for x in bar(1): pass
def test_unexpect_kwarg(self): bar = datatype('Bar', ['val']) with self.assertRaises(TypeError): bar(other=1)
class _Snapshots(datatype('_Snapshots', ['root'])): """Private singleton value to expose the snapshot directory (managed by rust) to python.
class SelectNode(datatype('SelectNode', ['subject', 'product', 'variants', 'variant_key']), Node): """A Node that selects a product for a subject. A Select can be satisfied by multiple sources, but fails if multiple sources produce a value. The 'variants' field represents variant configuration that is propagated to dependencies. When a task needs to consume a product as configured by the variants map, it uses the SelectVariant selector, which introduces the 'variant' value to restrict the names of values selected by a SelectNode. """ is_cacheable = False is_inlineable = True def _variants_node(self): if type(self.subject) is Address and self.product is not Variants: return SelectNode(self.subject, Variants, self.variants, None) return None def _select_literal(self, candidate, variant_value): """Looks for has-a or is-a relationships between the given value and the requested product. Returns the resulting product value, or None if no match was made. """ def items(): # Check whether the subject is-a instance of the product. yield candidate # Else, check whether it has-a instance of the product. if isinstance(candidate, HasProducts): for subject in candidate.products: yield subject # TODO: returning only the first literal configuration of a given type/variant. Need to # define mergeability for products. for item in items(): if not isinstance(item, self.product): continue if variant_value and not getattr(item, 'name', None) == variant_value: continue return item return None def step(self, step_context): # Request default Variants for the subject, so that if there are any we can propagate # them to task nodes. variants = self.variants variants_node = self._variants_node() if variants_node: dep_state = step_context.get(variants_node) if type(dep_state) is Waiting: return dep_state elif type(dep_state) is Return: # A subject's variants are overridden by any dependent's requested variants, so # we merge them left to right here. variants = Variants.merge(dep_state.value.default.items(), variants) # If there is a variant_key, see whether it has been configured. variant_value = None if self.variant_key: variant_values = [value for key, value in variants if key == self.variant_key] if variants else None if not variant_values: # Select cannot be satisfied: no variant configured for this key. return Noop('Variant key {} was not configured in variants {}', self.variant_key, variants) variant_value = variant_values[0] # If the Subject "is a" or "has a" Product, then we're done. literal_value = self._select_literal(self.subject, variant_value) if literal_value is not None: return Return(literal_value) # Else, attempt to use a configured task to compute the value. dependencies = [] matches = [] for dep in step_context.gen_nodes(self.subject, self.product, variants): dep_state = step_context.get(dep) if type(dep_state) is Waiting: dependencies.extend(dep_state.dependencies) elif type(dep_state) is Return: # We computed a value: see whether we can use it. literal_value = self._select_literal(dep_state.value, variant_value) if literal_value is not None: matches.append((dep, literal_value)) elif type(dep_state) is Throw: return dep_state elif type(dep_state) is Noop: continue else: State.raise_unrecognized(dep_state) # If any dependencies were unavailable, wait for them; otherwise, determine whether # a value was successfully selected. if dependencies: return Waiting(dependencies) elif len(matches) == 0: return Noop('No source of {}.', self) elif len(matches) > 1: # TODO: Multiple successful tasks are not currently supported. We should allow for this # by adding support for "mergeable" products. see: # https://github.com/pantsbuild/pants/issues/2526 return Throw(ConflictingProducersError.create(self.subject, self.product, matches)) else: return Return(matches[0][1])
def test_double_passed_arg(self): bar = datatype('Bar', ['val', 'zal']) with self.assertRaises(TypeError): bar(1, val=1)
class NonStringField(datatype([3])): pass expected_msg = "Type names and field names must be valid identifiers: '32'"
class InvalidTypeSpec(datatype([('a_field', 2)])): pass def test_instance_construction_by_repr(self):
class Link(datatype('Link', ['path']), Stat): """A symbolic link.""" def __new__(cls, path): return super(Link, cls).__new__(cls, six.binary_type(path))
class BuildDirs(datatype('BuildDirs', ['dependencies'])): """A list of Stat objects for directories containing build files."""
class File(datatype('File', ['path']), Stat): """A file.""" def __new__(cls, path): return super(File, cls).__new__(cls, six.binary_type(path))
class Dir(datatype('Dir', ['path']), Stat): """A directory.""" def __new__(cls, path): return super(Dir, cls).__new__(cls, six.binary_type(path))
class JVMPackageName(datatype(['name'])): """A typedef to represent a fully qualified JVM package name.""" pass
class Scrooge(datatype(['tool_address'])): """Placeholder for a Scrooge subsystem."""
class SourceRoots(datatype(['srcroots'])): """Placeholder for the SourceRoot subsystem."""
def test_atrs(self): bar = datatype(['val']) self.assertEqual(1, bar(1).val)
class BuildFiles(datatype('BuildFiles', ['files_content'])): """The FileContents of BUILD files in some directory"""
def test_replace_non_iterable(self): bar = datatype(['val', 'zal']) self.assertEqual(bar(1, 3), bar(1, 2)._replace(zal=3))
class BuildFileGlobs(datatype('BuildFilesGlobs', ['path_globs'])): """A wrapper around PathGlobs that are known to match a build file pattern."""
class JustTypeField(datatype([str])): pass expected_msg = "Type names and field names must be valid identifiers: '3'"
class BundlesField( datatype(['address', 'bundles', 'filespecs_list', 'path_globs_list']), Field): """Represents the `bundles` argument, each of which has a PathGlobs to represent its `fileset`.""" def __hash__(self): return hash(self.address)
class HostLibcDev(datatype(['crti_object', 'fingerprint'])): pass
def test_not_iterable(self): bar = datatype('Bar', ['val']) with self.assertRaises(TypeError): for x in bar(1): pass
class LegacyBuildGraphNode( datatype('LegacyGraphNode', ['target_adaptor', 'dependency_addresses'])): """A Node to represent a node in the legacy BuildGraph.
def test_invalid_field_name(self): with self.assertRaises(ValueError): datatype('Bar', ['0isntanallowedfirstchar'])
def test_too_many_args(self): bar = datatype('Bar', ['val', 'zal']) with self.assertRaises(TypeError): bar(1, 1, 1)
class MultipleSameName(datatype([ 'field_a', 'field_b', 'field_a', ])): pass
class LegacyTarget(datatype('LegacyTarget', ['adaptor', 'dependencies'])): """A class to represent a node and edges in the legacy BuildGraph.
class SnapshottedProcessResult( datatype('SnapshottedProcessResult', ['stdout', 'stderr', 'exit_code'])): """Contains the stdout, stderr and exit code from executing a process."""
def test_properties_not_assignable(self): bar = datatype('Bar', ['val']) bar_inst = bar(1) with self.assertRaises(AttributeError): bar_inst.val = 2
class HydratedField(datatype('HydratedField', ['name', 'value'])): """A wrapper for a fully constructed replacement kwarg for a LegacyTarget."""
def test_type_included_in_eq(self): foo = datatype(['val']) bar = datatype(['val']) self.assertFalse(foo(1) == bar(1)) self.assertTrue(foo(1) != bar(1))
class UnpackedArchives( datatype([('found_files', tuple), ('rel_unpack_dir', text_type)])): def __new__(cls, found_files, rel_unpack_dir): return super(UnpackedArchives, cls).__new__(cls, tuple(found_files), text_type(rel_unpack_dir))
class NonStringTypeField(datatype([(32, int)])): pass expected_msg = "Encountered duplicate field name: 'field_a'"
class Handle(datatype([('pid', int), ('port', int)])): """A handle to a "probably running" pantsd instance.
def test_deep_copy(self): # deep copy calls into __getnewargs__, which namedtuple defines as implicitly using __iter__. bar = datatype(['val']) self.assertEqual(bar(1), copy.deepcopy(bar(1)))
def test_unexpect_kwarg(self): bar = datatype(['val']) with self.assertRaisesWithMessageContaining( TypeError, "__new__() got an unexpected keyword argument 'other'"): bar(other=1)
def test_as_dict(self): bar = datatype(['val']) self.assertEqual({'val': 1}, bar(1)._asdict())
class NoFields(datatype()): pass expected_msg = "Type names and field names must be valid identifiers: \"<class 'str'>\""
def test_properties_not_assignable(self): bar = datatype(['val']) bar_inst = bar(1) with self.assertRaisesWithMessage(AttributeError, "can't set attribute"): bar_inst.val = 2
class CToolchain(datatype([('c_compiler', CCompiler), ('c_linker', Linker)])): pass
def test_mixed_argument_types(self): bar = datatype(['val', 'zal']) self.assertEqual(bar(1, 2), bar(val=1, zal=2)) self.assertEqual(bar(1, 2), bar(zal=2, val=1))
class CppToolchain( datatype([('cpp_compiler', CppCompiler), ('cpp_linker', Linker)])): pass
class JarDependency( datatype([ 'org', 'base_name', 'rev', 'force', 'ext', 'url', 'apidocs', 'classifier', 'mutable', 'intransitive', 'excludes', 'base_path' ])): """A pre-built Maven repository dependency. This is the developer facing api, compared to the context wrapper class `JarDependencyParseContextWrapper`, which exposes api through build file to users. The only additional parameter `base_path` here is so that we can retrieve the file URL in its absolute (for ivy) or relative (for fingerprinting) form. The context wrapper class determines the `base_path` from where `jar` is defined at. If a relative file url is provided, its absolute form will be (`buildroot` + `base_path` + relative url). :API: public """ @staticmethod def _prepare_excludes(excludes): return tuple( assert_list(excludes, expected_type=Exclude, can_be_none=True, key_arg='excludes', allowable=( tuple, list, ))) def __new__(cls, org, name, rev=None, force=False, ext=None, url=None, apidocs=None, classifier=None, mutable=None, intransitive=False, excludes=None, base_path=None): """ :param string base_path: base path that's relative to the build root. """ excludes = JarDependency._prepare_excludes(excludes) base_path = base_path or '.' if os.path.isabs(base_path): base_path = os.path.relpath(base_path, get_buildroot()) return super(JarDependency, cls).__new__(cls, org=org, base_name=name, rev=rev, force=force, ext=ext, url=url, apidocs=apidocs, classifier=classifier, mutable=mutable, intransitive=intransitive, excludes=excludes, base_path=base_path) @property def name(self): return self.base_name @memoized_method def get_url(self, relative=False): if self.url: parsed_url = parse.urlparse(self.url) if parsed_url.scheme == 'file': if relative and os.path.isabs(parsed_url.path): relative_path = os.path.relpath( parsed_url.path, os.path.join(get_buildroot(), self.base_path)) return 'file:{path}'.format( path=os.path.normpath(relative_path)) if not relative and not os.path.isabs(parsed_url.path): abs_path = os.path.join(get_buildroot(), self.base_path, parsed_url.path) return 'file://{path}'.format( path=os.path.normpath(abs_path)) return self.url @property def transitive(self): return not self.intransitive def copy(self, **replacements): """Returns a clone of this JarDependency with the given replacements kwargs overlaid.""" cls = type(self) kwargs = self._asdict() for key, val in replacements.items(): if key == 'excludes': val = JarDependency._prepare_excludes(val) kwargs[key] = val org = kwargs.pop('org') base_name = kwargs.pop('base_name') return cls(org, base_name, **kwargs) def __str__(self): return 'JarDependency({})'.format(self.coordinate) @memoized_property def coordinate(self): """Returns the maven coordinate of this jar. :rtype: :class:`pants.java.jar.M2Coordinate` """ return M2Coordinate(org=self.org, name=self.name, rev=self.rev, classifier=self.classifier, ext=self.ext) def cache_key(self): excludes = [(e.org, e.name) for e in self.excludes] return stable_json_hash( dict(org=self.org, name=self.name, rev=self.rev, force=self.force, ext=self.ext, url=self.get_url(relative=True), classifier=self.classifier, transitive=self.transitive, mutable=self.mutable, excludes=excludes))
class MultipleSameNameWithType(datatype([ 'field_a', ('field_a', int), ])): pass
class Assembler(datatype([ 'path_entries', 'exe_filename', 'library_dirs', ]), Executable): pass
def _synthesize_goal_product(name): product_type_name = '{}GoalExecution'.format(name.capitalize()) if PY2: product_type_name = product_type_name.encode('utf-8') return type(product_type_name, (datatype(['result']),), {})