Example #1
0
    def test_classpath_by_targets(self):
        b = self.make_target("b", JvmTarget)
        a = self.make_target("a",
                             JvmTarget,
                             dependencies=[b],
                             excludes=[Exclude("com.example", "lib")])

        classpath_products = ClasspathProducts(self.pants_workdir)

        path1 = self._path("jar/path1")
        path2 = self._path("jar/path2")
        path3 = os.path.join(self.pants_workdir, "jar/path3")
        resolved_jar = ResolvedJar(
            M2Coordinate(org="com.example", name="lib", rev="1.0"),
            cache_path="somewhere",
            pants_path=path3,
        )
        classpath_products.add_for_target(a, [("default", path1)])
        classpath_products.add_for_target(a, [("non-default", path2)])
        classpath_products.add_for_target(b, [("default", path2)])
        classpath_products.add_jars_for_targets([b], "default", [resolved_jar])
        classpath_products.add_excludes_for_targets([a])

        # (a, path2) filtered because of conf
        # (b, path3) filtered because of excludes
        self.assertEqual(
            OrderedDict([(a, [ClasspathEntry(path1)]),
                         (b, [ClasspathEntry(path2)])]),
            ClasspathUtil.classpath_by_targets(a.closure(bfs=True),
                                               classpath_products),
        )
Example #2
0
 def create_compile_context(self, target, target_workdir):
   # workdir layout:
   # rsc/
   #   - outline/ -- semanticdbs for the current target as created by rsc
   #   - m.jar    -- reified scala signature jar
   # zinc/
   #   - classes/   -- class files
   #   - z.analysis -- zinc analysis for the target
   #   - z.jar      -- final jar for the target
   #   - zinc_args  -- file containing the used zinc args
   sources = self._compute_sources_for_target(target)
   rsc_dir = os.path.join(target_workdir, "rsc")
   zinc_dir = os.path.join(target_workdir, "zinc")
   return [
     RscCompileContext(
       target=target,
       analysis_file=None,
       classes_dir=None,
       jar_file=None,
       zinc_args_file=None,
       rsc_jar_file=os.path.join(rsc_dir, 'm.jar'),
       log_dir=os.path.join(rsc_dir, 'logs'),
       sources=sources,
       workflow=self._classify_target_compile_workflow(target),
     ),
     CompileContext(
       target=target,
       analysis_file=os.path.join(zinc_dir, 'z.analysis'),
       classes_dir=ClasspathEntry(os.path.join(zinc_dir, 'classes'), None),
       jar_file=ClasspathEntry(os.path.join(zinc_dir, 'z.jar'), None),
       log_dir=os.path.join(zinc_dir, 'logs'),
       zinc_args_file=os.path.join(zinc_dir, 'zinc_args'),
       sources=sources,
     )
   ]
    def test_classpath_by_targets(self):
        b = self.make_target('b', JvmTarget)
        a = self.make_target('a',
                             JvmTarget,
                             dependencies=[b],
                             excludes=[Exclude('com.example', 'lib')])

        classpath_products = ClasspathProducts(self.pants_workdir)

        path1 = self._path('jar/path1')
        path2 = self._path('jar/path2')
        path3 = os.path.join(self.pants_workdir, 'jar/path3')
        resolved_jar = ResolvedJar(M2Coordinate(org='com.example',
                                                name='lib',
                                                rev='1.0'),
                                   cache_path='somewhere',
                                   pants_path=path3)
        classpath_products.add_for_target(a, [('default', path1)])
        classpath_products.add_for_target(a, [('non-default', path2)])
        classpath_products.add_for_target(b, [('default', path2)])
        classpath_products.add_jars_for_targets([b], 'default', [resolved_jar])
        classpath_products.add_excludes_for_targets([a])

        # (a, path2) filtered because of conf
        # (b, path3) filtered because of excludes
        self.assertEqual(
            OrderedDict([(a, [ClasspathEntry(path1)]),
                         (b, [ClasspathEntry(path2)])]),
            ClasspathUtil.classpath_by_targets(a.closure(bfs=True),
                                               classpath_products))
    def test_get_product_target_mappings_for_targets_intransitive(self):
        b = self.make_target('b',
                             JvmTarget,
                             excludes=[Exclude('com.example', 'lib')])
        a = self.make_target('a', JvmTarget, dependencies=[b])

        classpath_product = ClasspathProducts(self.pants_workdir)
        example_jar_path = self._example_jar_path()
        resolved_jar = self.add_jar_classpath_element_for_path(
            classpath_product, a, example_jar_path)

        classpath_product.add_for_target(
            b, [('default', self.path('b/loose/classes/dir'))])
        classpath_product.add_for_target(
            a, [('default', self.path('a/loose/classes/dir')),
                ('default', self.path('an/internally/generated.jar'))])

        classpath_target_tuples = classpath_product.get_product_target_mappings_for_targets(
            [a])
        self.assertEqual([
            (('default',
              ArtifactClasspathEntry(example_jar_path, resolved_jar.coordinate,
                                     resolved_jar.cache_path)), a),
            (('default', ClasspathEntry(self.path('a/loose/classes/dir'))), a),
            (('default',
              ClasspathEntry(self.path('an/internally/generated.jar'))), a)
        ], classpath_target_tuples)
Example #5
0
    def test_get_internal_classpath_entries_for_targets(self):
        b = self.make_target("b", JvmTarget)
        a = self.make_target("a", JvmTarget, dependencies=[b])

        classpath_product = ClasspathProducts(self.pants_workdir)

        # This artifact classpath entry should be ignored.
        example_jar_path = self._example_jar_path()
        self.add_jar_classpath_element_for_path(classpath_product, a,
                                                example_jar_path)

        classpath_product.add_for_target(
            b, [("default", self.path("b/loose/classes/dir"))])
        classpath_product.add_for_target(
            a,
            [
                ("default", self.path("a/loose/classes/dir")),
                ("default", self.path("an/internally/generated.jar")),
            ],
        )

        classpath = classpath_product.get_internal_classpath_entries_for_targets(
            a.closure(bfs=True))
        self.assertEqual(
            [
                ("default", ClasspathEntry(self.path("a/loose/classes/dir"))),
                ("default",
                 ClasspathEntry(self.path("an/internally/generated.jar"))),
                ("default", ClasspathEntry(self.path("b/loose/classes/dir"))),
            ],
            classpath,
        )
Example #6
0
 def create_compile_context(self, target, target_workdir):
   return CompileContext(target=target,
                         analysis_file=os.path.join(target_workdir, 'z.analysis'),
                         classes_dir=ClasspathEntry(os.path.join(target_workdir, 'classes')),
                         jar_file=ClasspathEntry(os.path.join(target_workdir, 'z.jar')),
                         log_dir=os.path.join(target_workdir, 'logs'),
                         args_file=os.path.join(target_workdir, 'zinc_args'),
                         post_compile_merge_dir=os.path.join(target_workdir,
                                                             'post_compile_merge_dir'),
                         sources=self._compute_sources_for_target(target))
Example #7
0
    def execute(self):
        # Tracked and returned for use in tests.
        # TODO: Rewrite those tests. execute() is not supposed to return anything.
        processed_targets = []

        compile_classpath = self.context.products.get_data("compile_classpath")
        runtime_classpath = self.context.products.get_data(
            "runtime_classpath", compile_classpath.copy)

        all_relevant_resources_targets = self.find_all_relevant_resources_targets(
        )
        if not all_relevant_resources_targets:
            return processed_targets

        with self.invalidated(
                targets=all_relevant_resources_targets,
                fingerprint_strategy=self.create_invalidation_strategy(),
                invalidate_dependents=False,
                topological_order=False,
        ) as invalidation:
            for vt in invalidation.invalid_vts:
                # Generate resources to the chroot.
                self.prepare_resources(vt.target, vt.results_dir)
                processed_targets.append(vt.target)
            for vt, digest in self._capture_resources(invalidation.all_vts):
                # Register the target's chroot in the products.
                for conf in self.get_options().confs:
                    runtime_classpath.add_for_target(
                        vt.target,
                        [(conf, ClasspathEntry(vt.results_dir, digest))])

        return processed_targets
Example #8
0
    def _compile_compiler_bridge(self, context):
        """Compile the compiler bridge to be used by zinc, using our scala bootstrapper. It will
        compile and cache the jar, and materialize it if not already there.

        :param context: The context of the task trying to compile the bridge.
                        This is mostly needed to use its scheduler to create digests of the relevant jars.
        :return: The absolute path to the compiled scala-compiler-bridge jar.
        """
        bridge_jar_name = "scala-compiler-bridge.jar"
        bridge_jar = os.path.join(self._compiler_bridge_cache_dir, bridge_jar_name)
        global_bridge_cache_dir = os.path.join(
            self._zinc_factory.get_options().pants_bootstrapdir,
            fast_relpath(self._compiler_bridge_cache_dir, self._workdir()),
        )
        globally_cached_bridge_jar = os.path.join(global_bridge_cache_dir, bridge_jar_name)

        # Workaround to avoid recompiling the bridge for every integration test
        # We check the bootstrapdir (.cache) for the bridge.
        # If it exists, we make a copy to the buildroot.
        #
        # TODO Remove when action caches are implemented.
        if os.path.exists(globally_cached_bridge_jar):
            # Cache the bridge jar under buildroot, to allow snapshotting
            safe_mkdir(self._relative_to_buildroot(self._compiler_bridge_cache_dir))
            safe_hardlink_or_copy(globally_cached_bridge_jar, bridge_jar)

        if not os.path.exists(bridge_jar):
            res = self._run_bootstrapper(bridge_jar, context)
            context._scheduler.materialize_directory(
                DirectoryToMaterialize(res.output_directory_digest)
            )
            # For the workaround above to work, we need to store a copy of the bridge in
            # the bootstrapdir cache (.cache).
            safe_mkdir(global_bridge_cache_dir)
            safe_hardlink_or_copy(bridge_jar, globally_cached_bridge_jar)

            return ClasspathEntry(bridge_jar, res.output_directory_digest)
        else:
            bridge_jar_snapshot = context._scheduler.capture_snapshots(
                (
                    PathGlobsAndRoot(
                        PathGlobs((self._relative_to_buildroot(bridge_jar),)), get_buildroot()
                    ),
                )
            )[0]
            bridge_jar_digest = bridge_jar_snapshot.directory_digest
            return ClasspathEntry(bridge_jar, bridge_jar_digest)
Example #9
0
 def create_compile_context(self, target, target_workdir):
     # workdir layout:
     # rsc/
     #   - outline/ -- semanticdbs for the current target as created by rsc
     #   - m.jar    -- reified scala signature jar, also used for scalac -Youtline
     # zinc/
     #   - classes/   -- class files
     #   - z.analysis -- zinc analysis for the target
     #   - z.jar      -- final jar for the target
     #   - zinc_args  -- file containing the used zinc args
     sources = self._compute_sources_for_target(target)
     rsc_dir = os.path.join(target_workdir, "rsc")
     zinc_dir = os.path.join(target_workdir, "zinc")
     return self.RscZincMergedCompileContexts(
         rsc_cc=RscCompileContext(
             target=target,
             # The analysis_file and classes_dir are not supposed to be useful
             # It's a hacky way of preserving most of the logic in zinc_compile.py
             # While allowing it to use RscCompileContexts for outlining.
             analysis_file=os.path.join(rsc_dir, "z.analysis.outline"),
             classes_dir=ClasspathEntry(
                 os.path.join(rsc_dir, "zinc_classes"), None),
             jar_file=None,
             args_file=os.path.join(rsc_dir, "rsc_args"),
             rsc_jar_file=ClasspathEntry(os.path.join(rsc_dir, "m.jar")),
             log_dir=os.path.join(rsc_dir, "logs"),
             post_compile_merge_dir=os.path.join(rsc_dir,
                                                 "post_compile_merge_dir"),
             sources=sources,
             workflow=self._classify_target_compile_workflow(target),
             diagnostics_out=None,
         ),
         zinc_cc=CompileContext(
             target=target,
             analysis_file=os.path.join(zinc_dir, "z.analysis"),
             classes_dir=ClasspathEntry(os.path.join(zinc_dir, "classes"),
                                        None),
             jar_file=ClasspathEntry(os.path.join(zinc_dir, "z.jar"), None),
             log_dir=os.path.join(zinc_dir, "logs"),
             args_file=os.path.join(zinc_dir, "zinc_args"),
             post_compile_merge_dir=os.path.join(zinc_dir,
                                                 "post_compile_merge_dir"),
             sources=sources,
             diagnostics_out=os.path.join(zinc_dir, "diagnostics.json"),
         ),
     )
Example #10
0
 def _nailgun_server_classpath_entry(self):
     nailgun_jar = self.tool_jar('nailgun-server')
     nailgun_jar_snapshot, = self.context._scheduler.capture_snapshots(
         (PathGlobsAndRoot(
             PathGlobs((fast_relpath(nailgun_jar, get_buildroot()), )),
             get_buildroot()), ))
     nailgun_jar_digest = nailgun_jar_snapshot.directory_digest
     return ClasspathEntry(nailgun_jar, nailgun_jar_digest)
Example #11
0
 def _snapshotted_classpath(self, results_dir):
     relpath = fast_relpath(results_dir, get_buildroot())
     (classes_dir_snapshot, ) = self.context._scheduler.capture_snapshots([
         PathGlobsAndRoot(PathGlobs([relpath]), get_buildroot(),
                          Digest.load(relpath))
     ])
     return ClasspathEntry(results_dir,
                           classes_dir_snapshot.directory_digest)
Example #12
0
 def _memoized_scalac_classpath(self, scala_path, scheduler):
     snapshots = scheduler.capture_snapshots(
         tuple(
             PathGlobsAndRoot(PathGlobs([path]), get_buildroot())
             for path in scala_path))
     return [
         ClasspathEntry(path, snapshot)
         for path, snapshot in list(zip(scala_path, snapshots))
     ]
Example #13
0
    def test_get_product_target_mappings_for_targets_transitive(self):
        b = self.make_target("b",
                             JvmTarget,
                             excludes=[Exclude("com.example", "lib")])
        a = self.make_target("a", JvmTarget, dependencies=[b])

        classpath_product = ClasspathProducts(self.pants_workdir)
        example_jar_path = self._example_jar_path()
        resolved_jar = self.add_jar_classpath_element_for_path(
            classpath_product, a, example_jar_path)

        classpath_product.add_for_target(
            b, [("default", self.path("b/loose/classes/dir"))])
        classpath_product.add_for_target(
            a,
            [
                ("default", self.path("a/loose/classes/dir")),
                ("default", self.path("an/internally/generated.jar")),
            ],
        )

        classpath_target_tuples = classpath_product.get_product_target_mappings_for_targets(
            a.closure(bfs=True))
        self.assertEqual(
            [
                (
                    (
                        "default",
                        ArtifactClasspathEntry(example_jar_path,
                                               resolved_jar.coordinate,
                                               resolved_jar.cache_path),
                    ),
                    a,
                ),
                (("default", ClasspathEntry(
                    self.path("a/loose/classes/dir"))), a),
                (("default",
                  ClasspathEntry(
                      self.path("an/internally/generated.jar"))), a),
                (("default", ClasspathEntry(
                    self.path("b/loose/classes/dir"))), b),
            ],
            classpath_target_tuples,
        )
Example #14
0
 def _memoized_scalac_classpath(self, scala_path, scheduler):
     buildroot = get_buildroot()
     path_globs_and_roots = tuple(
         PathGlobsAndRoot(PathGlobs([fast_relpath(path, buildroot)]),
                          buildroot) for path in scala_path)
     snapshots = scheduler.capture_snapshots(path_globs_and_roots)
     return [
         ClasspathEntry(path, snapshot)
         for path, snapshot in list(zip(scala_path, snapshots))
     ]
Example #15
0
 def _wrap_path_elements(self, classpath_elements):
     wrapped_path_elements = []
     for element in classpath_elements:
         if len(element) != 2:
             raise ValueError("Input must be a list of tuples containing two elements.")
         if isinstance(element[1], ClasspathEntry):
             wrapped_path_elements.append(element)
         else:
             wrapped_path_elements.append((element[0], ClasspathEntry(element[1])))
     return wrapped_path_elements
Example #16
0
    def compute_classpath(cls, targets, classpath_products,
                          extra_classpath_tuples, confs):
        """Return the list of classpath entries for a classpath covering the passed targets.

    Filters and adds paths from extra_classpath_tuples to the end of the resulting list.

    As compute_classpath_entries but expects and returns strings, not ClasspathEntries.
    """
        return list(entry.path for entry in cls.compute_classpath_entries(
            targets, classpath_products, (
                (scope, ClasspathEntry(path))
                for scope, path in extra_classpath_tuples), confs))
Example #17
0
 def _shaded_jar_as_classpath_entry(self, shaded_jar):
     # Capture a Snapshot for the jar.
     buildroot = get_buildroot()
     snapshot = self.context._scheduler.capture_snapshots([
         PathGlobsAndRoot(
             PathGlobs([fast_relpath(shaded_jar, buildroot)]),
             buildroot,
             Digest.load(shaded_jar),
         )
     ])[0]
     snapshot.digest.dump(shaded_jar)
     return ClasspathEntry(shaded_jar, directory_digest=snapshot.digest)
Example #18
0
  def get_internal_classpath_entries_for_targets(self, targets, respect_excludes=True):
    """Gets the internal classpath products for the given targets.

    Products are returned in order, optionally respecting target excludes, and the products only
    include internal artifact classpath elements (ie: no resolved jars).

    :param targets: The targets to lookup classpath products for.
    :param bool respect_excludes: `True` to respect excludes; `False` to ignore them.
    :returns: The ordered (conf, classpath entry) tuples.
    :rtype: list of (string, :class:`ClasspathEntry`)
    """
    classpath_tuples = self.get_classpath_entries_for_targets(targets,
                                                              respect_excludes=respect_excludes)
    return [(conf, cp_entry) for conf, cp_entry in classpath_tuples
            if ClasspathEntry.is_internal_classpath_entry(cp_entry)]
  def get_internal_classpath_entries_for_targets(self, targets, respect_excludes=True):
    """Gets the internal classpath products for the given targets.

    Products are returned in order, optionally respecting target excludes, and the products only
    include internal artifact classpath elements (ie: no resolved jars).

    :param targets: The targets to lookup classpath products for.
    :param bool respect_excludes: `True` to respect excludes; `False` to ignore them.
    :returns: The ordered (conf, classpath entry) tuples.
    :rtype: list of (string, :class:`ClasspathEntry`)
    """
    classpath_tuples = self.get_classpath_entries_for_targets(targets,
                                                              respect_excludes=respect_excludes)
    return [(conf, cp_entry) for conf, cp_entry in classpath_tuples
            if ClasspathEntry.is_internal_classpath_entry(cp_entry)]
Example #20
0
 def to_classpath_entries(paths, scheduler):
   # list of path ->
   # list of (path, optional<digest>) ->
   path_and_digests = [(p, Digest.load(os.path.dirname(p))) for p in paths]
   # partition: list of path, list of tuples
   paths_without_digests = [p for (p, d) in path_and_digests if not d]
   if paths_without_digests:
     self.context.log.debug('Expected to find digests for {}, capturing them.'
       .format(paths_without_digests))
   paths_with_digests = [(p, d) for (p, d) in path_and_digests if d]
   # list of path -> list path, captured snapshot -> list of path with digest
   snapshots = scheduler.capture_snapshots(tuple(pathglob_for(p) for p in paths_without_digests))
   captured_paths_and_digests = [(p, s.directory_digest)
     for (p, s) in zip(paths_without_digests, snapshots)]
   # merge and classpath ify
   return [ClasspathEntry(p, d) for (p, d) in paths_with_digests + captured_paths_and_digests]
Example #21
0
    def _runtool_hermetic(self, main, tool_name, distribution, input_digest,
                          ctx):
        tool_classpath_abs = self._rsc_classpath
        tool_classpath = fast_relpath_collection(tool_classpath_abs)

        jvm_options = self._jvm_options

        if self._rsc.use_native_image:
            #jvm_options = []
            if jvm_options:
                raise ValueError(
                    "`{}` got non-empty jvm_options when running with a graal native-image, but this is "
                    "unsupported. jvm_options received: {}".format(
                        self.options_scope, safe_shlex_join(jvm_options)))
            native_image_path, native_image_snapshot = self._rsc.native_image(
                self.context)
            additional_snapshots = [native_image_snapshot]
            initial_args = [native_image_path]
        else:
            additional_snapshots = []
            initial_args = [
                distribution.java,
            ] + self.get_options().jvm_options + [
                '-cp',
                os.pathsep.join(tool_classpath),
                main,
            ]

        argfile_snapshot, = self.context._scheduler.capture_snapshots([
            PathGlobsAndRoot(
                PathGlobs([fast_relpath(ctx.args_file, get_buildroot())]),
                get_buildroot(),
            ),
        ])

        cmd = initial_args + ['@{}'.format(argfile_snapshot.files[0])]

        pathglobs = list(tool_classpath)

        if pathglobs:
            root = PathGlobsAndRoot(PathGlobs(tuple(pathglobs)),
                                    get_buildroot())
            # dont capture snapshot, if pathglobs is empty
            path_globs_input_digest = self.context._scheduler.capture_snapshots(
                (root, ))[0].directory_digest

        epr_input_files = self.context._scheduler.merge_directories(
            ((path_globs_input_digest, ) if path_globs_input_digest else ()) +
            ((input_digest, ) if input_digest else ()) +
            tuple(s.directory_digest for s in additional_snapshots) +
            (argfile_snapshot.directory_digest, ))

        epr = ExecuteProcessRequest(
            argv=tuple(cmd),
            input_files=epr_input_files,
            output_files=(fast_relpath(ctx.rsc_jar_file.path,
                                       get_buildroot()), ),
            output_directories=tuple(),
            timeout_seconds=15 * 60,
            description='run {} for {}'.format(tool_name, ctx.target),
            # TODO: These should always be unicodes
            # Since this is always hermetic, we need to use `underlying.home` because
            # ExecuteProcessRequest requires an existing, local jdk location.
            jdk_home=distribution.underlying_home,
        )
        res = self.context.execute_process_synchronously_without_raising(
            epr, self.name(), [WorkUnitLabel.COMPILER])

        if res.exit_code != 0:
            raise TaskError(res.stderr, exit_code=res.exit_code)

        # TODO: parse the output of -Xprint:timings for rsc and write it to self._record_target_stats()!

        res.output_directory_digest.dump(ctx.rsc_jar_file.path)

        ctx.rsc_jar_file = ClasspathEntry(ctx.rsc_jar_file.path,
                                          res.output_directory_digest)

        self.context._scheduler.materialize_directories((
            DirectoryToMaterialize(
                # NB the first element here is the root to materialize into, not the dir to snapshot
                get_buildroot(),
                res.output_directory_digest), ))

        return res
Example #22
0
 def _wrap_path_elements(self, classpath_elements):
   return [(element[0], ClasspathEntry(element[1])) for element in classpath_elements]
  def create_canonical_classpath(cls, classpath_products, targets, basedir,
                                 save_classpath_file=False,
                                 internal_classpath_only=True,
                                 excludes=None):
    """Create a stable classpath of symlinks with standardized names.

    By default symlinks are created for each target under `basedir` based on its `target.id`.
    Unique suffixes are added to further disambiguate classpath products from the same target.

    It also optionally saves the classpath products to be used externally (by intellij plugin),
    one output file for each target.

    Note calling this function will refresh the symlinks and output files for the target under
    `basedir` if they exist, but it will NOT delete/cleanup the contents for *other* targets.
    Caller wants that behavior can make the similar calls for other targets or just remove
    the `basedir` first.

    :param classpath_products: Classpath products.
    :param targets: Targets to create canonical classpath for.
    :param basedir: Directory to create symlinks.
    :param save_classpath_file: An optional file with original classpath entries that symlinks
      are created from.
    :param internal_classpath_only: whether to create symlinks just for internal classpath or
       all classpath.
    :param excludes: classpath entries should be excluded.

    :returns: Converted canonical classpath.
    :rtype: list of strings
    """
    def delete_old_target_output_files(classpath_prefix):
      """Delete existing output files or symlinks for target."""
      directory, basename = os.path.split(classpath_prefix)
      pattern = re.compile(r'^{basename}(([0-9]+)(\.jar)?|classpath\.txt)$'
                           .format(basename=re.escape(basename)))
      files = [filename for filename in os.listdir(directory) if pattern.match(filename)]
      for rel_path in files:
        path = os.path.join(directory, rel_path)
        if os.path.islink(path) or os.path.isfile(path):
          safe_delete(path)

    def prepare_target_output_folder(basedir, target):
      """Prepare directory that will contain canonical classpath for the target.

      This includes creating directories if it does not already exist, cleaning up
      previous classpath output related to the target.
      """
      output_dir = basedir
      # TODO(peiyu) improve readability once we deprecate the old naming style.
      # For example, `-` is commonly placed in string format as opposed to here.
      classpath_prefix_for_target = '{basedir}/{target_id}-'.format(basedir=basedir,
                                                                    target_id=target.id)

      if os.path.exists(output_dir):
        delete_old_target_output_files(classpath_prefix_for_target)
      else:
        os.makedirs(output_dir)
      return classpath_prefix_for_target

    excludes = excludes or set()
    canonical_classpath = []
    target_to_classpath = ClasspathUtil.classpath_by_targets(targets, classpath_products)

    processed_entries = set()
    for target, classpath_entries_for_target in target_to_classpath.items():
      if internal_classpath_only:
        classpath_entries_for_target = [entry for entry in classpath_entries_for_target
                                        if ClasspathEntry.is_internal_classpath_entry(entry)]
      if len(classpath_entries_for_target) > 0:
        classpath_prefix_for_target = prepare_target_output_folder(basedir, target)

        # Note: for internal targets pants has only one classpath entry, but user plugins
        # might generate additional entries, for example, build.properties for the target.
        # Also it's common to have multiple classpath entries associated with 3rdparty targets.
        for (index, entry) in enumerate(classpath_entries_for_target):
          if entry.is_excluded_by(excludes):
            continue

          # Avoid creating symlink for the same entry twice, only the first entry on
          # classpath will get a symlink. The resulted symlinks as a whole are still stable,
          # but may have non-consecutive suffixes because the 'missing' ones are those
          # have already been created symlinks by previous targets.
          if entry in processed_entries:
            continue
          processed_entries.add(entry)

          # Create a unique symlink path by prefixing the base file name with a monotonic
          # increasing `index` to avoid name collisions.
          _, ext = os.path.splitext(entry.path)
          symlink_path = '{}{}{}'.format(classpath_prefix_for_target, index, ext)
          real_entry_path = os.path.realpath(entry.path)
          if not os.path.exists(real_entry_path):
            raise MissingClasspathEntryError('Could not find {realpath} when attempting to link '
                                             '{src} into {dst}'
                                             .format(realpath=real_entry_path, src=entry.path, dst=symlink_path))

          os.symlink(real_entry_path, symlink_path)
          canonical_classpath.append(symlink_path)

        if save_classpath_file:
          classpath = [entry.path for entry in classpath_entries_for_target]
          with safe_open('{}classpath.txt'.format(classpath_prefix_for_target), 'w') as classpath_file:
            classpath_file.write(os.pathsep.join(classpath))
            classpath_file.write('\n')

    return canonical_classpath
Example #24
0
 def _set_directory_digest_for_compile_context(self, ctx, directory_digest):
   if self.get_options().use_classpath_jars:
     ctx.jar_file = ClasspathEntry(ctx.jar_file.path, directory_digest)
   else:
     ctx.classes_dir = ClasspathEntry(ctx.classes_dir.path, directory_digest)
Example #25
0
    def create_canonical_classpath(cls,
                                   classpath_products,
                                   targets,
                                   basedir,
                                   save_classpath_file=False,
                                   internal_classpath_only=True,
                                   excludes=None):
        """Create a stable classpath of symlinks with standardized names.

    By default symlinks are created for each target under `basedir` based on its `target.id`.
    Unique suffixes are added to further disambiguate classpath products from the same target.

    It also optionally saves the classpath products to be used externally (by intellij plugin),
    one output file for each target.

    Note calling this function will refresh the symlinks and output files for the target under
    `basedir` if they exist, but it will NOT delete/cleanup the contents for *other* targets.
    Caller wants that behavior can make the similar calls for other targets or just remove
    the `basedir` first.

    :param classpath_products: Classpath products.
    :param targets: Targets to create canonical classpath for.
    :param basedir: Directory to create symlinks.
    :param save_classpath_file: An optional file with original classpath entries that symlinks
      are created from.
    :param internal_classpath_only: whether to create symlinks just for internal classpath or
       all classpath.
    :param excludes: classpath entries should be excluded.

    :returns: Converted canonical classpath.
    :rtype: list of strings
    """
        def delete_old_target_output_files(classpath_prefix):
            """Delete existing output files or symlinks for target."""
            directory, basename = os.path.split(classpath_prefix)
            pattern = re.compile(
                r'^{basename}(([0-9]+)(\.jar)?|classpath\.txt)$'.format(
                    basename=re.escape(basename)))
            files = [
                filename for filename in os.listdir(directory)
                if pattern.match(filename)
            ]
            for rel_path in files:
                path = os.path.join(directory, rel_path)
                if os.path.islink(path) or os.path.isfile(path):
                    safe_delete(path)

        def prepare_target_output_folder(basedir, target):
            """Prepare directory that will contain canonical classpath for the target.

      This includes creating directories if it does not already exist, cleaning up
      previous classpath output related to the target.
      """
            output_dir = basedir
            # TODO(peiyu) improve readability once we deprecate the old naming style.
            # For example, `-` is commonly placed in string format as opposed to here.
            classpath_prefix_for_target = '{basedir}/{target_id}-'.format(
                basedir=basedir, target_id=target.id)

            if os.path.exists(output_dir):
                delete_old_target_output_files(classpath_prefix_for_target)
            else:
                os.makedirs(output_dir)
            return classpath_prefix_for_target

        excludes = excludes or set()
        canonical_classpath = []
        target_to_classpath = ClasspathUtil.classpath_by_targets(
            targets, classpath_products)

        processed_entries = set()
        for target, classpath_entries_for_target in target_to_classpath.items(
        ):
            if internal_classpath_only:
                classpath_entries_for_target = [
                    entry for entry in classpath_entries_for_target
                    if ClasspathEntry.is_internal_classpath_entry(entry)
                ]
            if len(classpath_entries_for_target) > 0:
                classpath_prefix_for_target = prepare_target_output_folder(
                    basedir, target)

                # Note: for internal targets pants has only one classpath entry, but user plugins
                # might generate additional entries, for example, build.properties for the target.
                # Also it's common to have multiple classpath entries associated with 3rdparty targets.
                for (index, entry) in enumerate(classpath_entries_for_target):
                    if entry.is_excluded_by(excludes):
                        continue

                    # Avoid creating symlink for the same entry twice, only the first entry on
                    # classpath will get a symlink. The resulted symlinks as a whole are still stable,
                    # but may have non-consecutive suffixes because the 'missing' ones are those
                    # have already been created symlinks by previous targets.
                    if entry in processed_entries:
                        continue
                    processed_entries.add(entry)

                    # Create a unique symlink path by prefixing the base file name with a monotonic
                    # increasing `index` to avoid name collisions.
                    _, ext = os.path.splitext(entry.path)
                    symlink_path = '{}{}{}'.format(classpath_prefix_for_target,
                                                   index, ext)
                    real_entry_path = os.path.realpath(entry.path)
                    if not os.path.exists(real_entry_path):
                        raise MissingClasspathEntryError(
                            'Could not find {realpath} when attempting to link '
                            '{src} into {dst}'.format(realpath=real_entry_path,
                                                      src=entry.path,
                                                      dst=symlink_path))

                    os.symlink(real_entry_path, symlink_path)
                    canonical_classpath.append(symlink_path)

                if save_classpath_file:
                    classpath = [
                        entry.path for entry in classpath_entries_for_target
                    ]
                    with safe_open(
                            '{}classpath.txt'.format(
                                classpath_prefix_for_target),
                            'w') as classpath_file:
                        classpath_file.write(os.pathsep.join(classpath))
                        classpath_file.write('\n')

        return canonical_classpath