Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get rid of Missing dependency warnings in java/scala compile. #37

Closed
tejal29 opened this issue Apr 8, 2014 · 9 comments
Closed

Get rid of Missing dependency warnings in java/scala compile. #37

tejal29 opened this issue Apr 8, 2014 · 9 comments

Comments

@tejal29
Copy link
Contributor

tejal29 commented Apr 8, 2014

./pants goal compile on a scala_library which includes java_sources spits this warning.

The below mentioned dependencies are actually provided by java_sources.

                       Missing BUILD dependency finagle/finagle-thrift/src/main/scala -> finagle/finagle-thrift/src/main/java because:
                           finagle/finagle-thrift/src/main/scala/com/twitter/finagle/thrift/ThriftClientBufferedCodec.scala uses finagle/finagle-thrift/src/main/java/com/twitter/finagle/thrift/ThriftClientRequest.java
                       Missing BUILD dependency finagle/finagle-zipkin/src/main/scala -> finagle/finagle-thrift/src/main/java because:
                           finagle/finagle-zipkin/src/main/scala/com/twitter/finagle/zipkin/thrift/Endpoint.scala uses finagle/finagle-thrift/src/main/java/com/twitter/finagle/thrift/thrift/Endpoint.java
                           finagle/finagle-zipkin/src/main/scala/com/twitter/finagle/zipkin/thrift/Span.scala uses finagle/finagle-thrift/src/main/java/com/twitter/finagle/thrift/thrift/Annotation.java
                           finagle/finagle-zipkin/src/main/scala/com/twitter/finagle/zipkin/thrift/Span.scala uses finagle/finagle-thrift/src/main/java/com/twitter/finagle/thrift/thrift/BinaryAnnotation.java
                           finagle/finagle-zipkin/src/main/scala/com/twitter/finagle/zipkin/thrift/Span.scala uses finagle/finagle-thrift/src/main/java/com/twitter/finagle/thrift/thrift/Endpoint.java
                           finagle/finagle-zipkin/src/main/scala/com/twitter/finagle/zipkin/thrift/Span.scala uses finagle/finagle-thrift/src/main/java/com/twitter/finagle/thrift/thrift/Span.java
@tejal29
Copy link
Contributor Author

tejal29 commented Apr 15, 2014

Submitted

@tejal29 tejal29 closed this as completed Apr 15, 2014
@benjyw
Copy link
Sponsor Contributor

benjyw commented Apr 15, 2014

What was the commit that fixed this? That missing dep detection is my code, so I'd like to review the change.

@tejal29 tejal29 reopened this Apr 15, 2014
@tejal29
Copy link
Contributor Author

tejal29 commented Apr 15, 2014

I think this is by mistake.. i wanted to close to some other issue.
ended up closing this.
I am not working on this.

@johanoskarsson
Copy link
Contributor

I pushed a simple branch with a java_library that directly depends on a scala_library. The java file uses a class in the scala file. This currently triggers an incorrect build dependency missing warning.
It looks like jmake_analysis_parser.py assumes the "classes dir" is .pants.d/compile/jvm/java/... for all the classes, when in fact it is .../jvm/scala in for one of them in this case.

For example it thinks the class is here:
u'/Users/johan/Dev/pants/.pants.d/compile/jvm/java/classes/com/pants/testproject/javadepsonscala/Scala.class'

When it is in fact here:
u'/Users/johan/Dev/pants/.pants.d/compile/jvm/scala/classes/com/pants/testproject/javadepsonscala/Scala.class'

@johanoskarsson
Copy link
Contributor

Looks like I forgot the link to the branch: https://github.com/pantsbuild/pants/tree/javadep

@ericzundel
Copy link
Member

I notice a very similar issue when using an annotation processor:

09:22:02 00:26           [scan_deps]
                       Missing BUILD dependency service/container/annotation-processors/src/main/java:app-config-processor -> .pants.d/compile/jvm/apt/classes/com/squareup/common/protobuf/ServiceMethodSpec.class
                       Missing BUILD dependency service/container/annotation-processors/src/main/java:app-config-processor -> .pants.d/compile/jvm/apt/classes/com/squareup/common/processors/BaseProcessor.class
                       Missing BUILD dependency service/container/annotation-processors/src/main/java:app-config-processor -> .pants.d/compile/jvm/apt/classes/com/squareup/config/AppManifest.class
                       Missing BUILD dependency service/container/annotation-processors/src/main/java:app-config-processor -> .pants.d/compile/jvm/apt/classes/com/squareup/common/protobuf/ServiceReflection.class

And Garrett was noticing something on the zip file work he's doing.

@jsirois
Copy link
Contributor

jsirois commented Jun 5, 2014

Ok - I think the issue is like so:

In jvm_compile the key top-level flow is:

            # Update the products with the latest classes. Must happen before the
            # missing dependencies check.
            self._register_products(vts.targets, analysis_file)
            if self._dep_analyzer:
              # Check for missing dependencies.
              actual_deps = self._analysis_parser.parse_deps_from_path(analysis_file,
                  lambda: self._compute_classpath_elements_by_class(cp_entries))
              with self.context.new_workunit(name='find-missing-dependencies'):
                self._dep_analyzer.check(sources, actual_deps)

So, in-short:

  1. compute actual_deps for this compile round from compiler analysis file data
  2. check if the BUILD deps square with the actual deps

Now in 1 the analysis file parser is handed 2 pieces of data - the analysis file for this compile round and a function that can produce an mapping of all known classes to the the jar or local target that provides them (there the local target is a slice of a global classfile output dir).

The indexing function looks like so:

def _compute_classpath_elements_by_class(self, classpath):
    # Don't consider loose classes dirs in our classpath. Those will be considered
    # separately, by looking at products.
    def non_product(path):
      return not (path.startswith(self._pants_workdir) and os.path.isdir(path))
    classpath_jars = filter(non_product, classpath)
    if self._class_to_jarfile is None:
      self._class_to_jarfile = {}
      for jarpath in self.find_all_bootstrap_jars() + classpath_jars:
        # Per the classloading spec, a 'jar' in this context can also be a .zip file.
        if os.path.isfile(jarpath) and ((jarpath.endswith('.jar') or jarpath.endswith('.zip'))):
          with open_zip(jarpath, 'r') as jar:
            for cls in jar.namelist():
              # First jar with a given class wins, just like when classloading.
              if cls.endswith(b'.class') and not cls in self._class_to_jarfile:
                self._class_to_jarfile[cls] = jarpath
        elif os.path.isdir(jarpath):
          for dirpath, _, filenames in os.walk(jarpath, followlinks=True):
            for f in filter(lambda x: x.endswith('.class'), filenames):
              cls = os.path.relpath(os.path.join(dirpath, f), jarpath)
              if not cls in self._class_to_jarfile:
                self._class_to_jarfile[cls] = jarp

The comment is key and true even though the impl is confusing since the elif branch looks like it indexes loose classes - which it does, but it only does for loose classes outside .pants.d/ which AFAICT never exist.

So with the comment in mind and drilling into step 1 (self._analysis_parser.parse_deps_from_path(analysis_file, lambda:...)) we land ~directly at this code:

def parse_deps(self, infile, classpath_indexer):
    buildroot = get_buildroot()
    classpath_elements_by_class = classpath_indexer()
    self._expect_header(infile.readline(), 'pcd entries')
    num_pcd_entries = self._parse_num_items(infile.readline())
    for _ in xrange(0, num_pcd_entries):
      infile.readline()  # Skip these lines.
    src_to_deps = self._parse_deps_at_position(infile)
    ret = defaultdict(set)
    for src, deps in src_to_deps.items():
      for dep in deps:
        rel_classfile = dep + '.class'
        classpath_element = classpath_elements_by_class.get(rel_classfile, None)
        if classpath_element:  # Dep is on an external jar/classes dir.
          ret[src].add(classpath_element)
        else:  # Dep is on an internal class.
          classfile = os.path.join(buildroot, self.classes_dir, rel_classfile)
          ret[src].add(classfile)
    return ret

Up top the index classpath_elements_by_class is built. That's the bit computed ignoring "...loose classes dirs in our classpath." So its just an index of classes in classpath jars. That's critical because then there is a fork below in the innermost loop below that either gets an index hit and registers the prior or else a miss and assumes "Dep is on an internal class." and furthermore it assume in the path join code that the internal class was produced by the current compiler. Thats the bug since the internal class in this multi-compiler GroupTask could come from any group member in a prior round, not necessarily the current group member.

My prediction then is that any cross-jvm-compiler internal dep will fail dep checks erroneously.

@jsirois
Copy link
Contributor

jsirois commented Jun 5, 2014

My prediction is wrong but thats because the parse_deps impl in the ZincAnalysisParser is different of course as is the zinc analysis file. The zinc analysis file comes with absolute paths everywhere making things easy on pants; ie:

binary dependencies:
3 items
/home/jsirois/dev/3rdparty/pantsbuild-pants/src/scala/com/pants/testproject/scaladepsonjava/Scala.scala -> /home/jsirois/dev/3rdparty/pantsbuild-pants/.pants.d/compile/jvm/java/classes/com/pants/testproject/scaladepsonjava/Java.class
/home/jsirois/dev/3rdparty/pantsbuild-pants/src/scala/com/pants/testproject/scaladepsonjava/Scala.scala -> /home/jsirois/dev/3rdparty/pantsbuild-pants/.pants.d/ivy/jars/org.scala-lang/scala-library/jars/scala-library-2.9.3.jar
/home/jsirois/dev/3rdparty/pantsbuild-pants/src/scala/com/pants/testproject/scaladepsonjava/Scala.scala -> /opt/jdk1.7.0_55/jre/lib/rt.jar

The JMakeAnalysisParser has to work with this:

dependencies:
1 items
/home/jsirois/dev/3rdparty/pantsbuild-pants/src/java/com/pants/testproject/javadepsonscala/Java.java    com/pants/testproject/javadepsonscala/Scala     java/lang/Object        com/pants/testproject/javadepsonscala/Java

Thus the need to fill in the com/pants/testproject/javadepsonscala/Scala path prefix and the wrong assumption that the prefix is the current (jmake) compile output dir.

@johanoskarsson
Copy link
Contributor

Posted a RB here: https://rbcommons.com/s/twitter/r/474/

@tejal29 tejal29 closed this as completed Jun 25, 2014
WamBamBoozle pushed a commit to WamBamBoozle/pants that referenced this issue Apr 23, 2015
Format implicits are no longer needed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants