-
Notifications
You must be signed in to change notification settings - Fork 278
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
Use a scala compiler plugin to throw errors on missing direct-deps #243
Conversation
Can one of the admins verify this patch? |
Hi @kchodorow , we want to add a scala compiler plugin to the rules-scala repo, but we cannot build it directly with
Is there some other way you can think of that resolves the cyclic dependency here? |
@johnynek I'll do a detailed code review but I'd appreciate your general feedback about this direction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, this is very exciting. I'd have two main requests:
-
can we keep the bootstrap compiler local to this repo (by parameterizing the skylark rules and then making a scala_library_bootstrap rule or something which can produce jars that scala_library can depend on).
-
can we remove all the whitespace changes from this diff to make reviewing less error prone/burdensome.
WORKSPACE
Outdated
@@ -1,5 +1,16 @@ | |||
workspace(name = "io_bazel_rules_scala") | |||
|
|||
|
|||
####### previous rules scala for building of scala compiler plugin | |||
rules_scala_version="3070353d06bb726141e6cea241e5d6e355a4d14f" # update this as needed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this approach here. It makes it more difficult to have your own branch of rules.
Could we instead have a bootstrap
compiler mode that is very simple, but possibly slow that does not depend on fancy features? Or maybe just an option we can pass into the skylark which can use the direct/indirect approach at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed http_archive of old rules_scala version.
added bootstrapping version of scala_library rule
@@ -0,0 +1,4 @@ | |||
<plugin> | |||
<name>dependency-analyzer</name> | |||
<classname>plugin.src.main.scala.io.github.retronym.dependencyanalyzer.DependencyAnalyzer</classname> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this used for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a scala compiler plugin descriptor that contains the entry point for the plugin.
See http://www.scala-lang.org/old/node/140
@@ -0,0 +1,100 @@ | |||
package plugin.src.main.scala.io.github.retronym.dependencyanalyzer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we use a different package name when we are doing this? It seems weird to have a copy of the code without putting it in a new package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed
import scala.tools.nsc.plugins.{Plugin, PluginComponent} | ||
import scala.tools.nsc.{Global, Phase} | ||
|
||
class DependencyAnalyzer(val global: Global) extends Plugin with Compat { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this exactly the same code with no changes? If so, can we add a comment to that effect and if not explain where it is different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have referred this question to Google open source office
else s"-P:dependency-analyzer:$name:${values.mkString(":")}" | ||
} | ||
|
||
object Coursier { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this test is doing network ops, no? That seems suboptimal to me if so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnynek That is correct, we noted this issue in the description...
This coming week, a couple of Wix developers are working on this feature.
Their first task is to move the tests in this unit test class to the the E2E test run by the test_run
script. this way using Coursier will not be neccessary
As this is is the only issue waiting to be resolved in the coming week, we would appreciate it if you can give your OK to merge this PR, in order for the new developers to work on a new branch out of master
s"error on $target should not appear in errors!") | ||
} | ||
|
||
implicit class `decode bazel lables`(targetLabel: String) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lables
is misspelled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed spelling
scala/scala.bzl
Outdated
@@ -644,6 +679,8 @@ scala_library = rule( | |||
attrs={ | |||
"main_class": attr.string(), | |||
"exports": attr.label_list(allow_files=False), | |||
"enable_dependency_analyzer": attr.bool(default=True, mandatory=False), | |||
"dependency_analyzer_plugin": attr.label(default=Label("//plugin/src/main:dependency_analyzer"), allow_files=_jar_filetype, mandatory=False), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you may need a full @io_bazelbuild_rules_scala//plugin/...
name here since it may not be expanded correctly in remote repos.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added repo prefix
|
||
String[] pluginParams; | ||
if (ops.enableDependencyAnalyzer) { | ||
pluginParams = pluginParamsInUse; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we move 197 here to make it easier to verify that when we don't use the dependency analyzer we won't use the options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -280,38 +301,39 @@ private static final Path newArgFile(CompileOptions ops, String[] javaSources, P | |||
private static void removeTmp(Path tmp) throws IOException { | |||
if (tmp != null) { | |||
Files.walkFileTree(tmp, new SimpleFileVisitor<Path>() { | |||
@Override |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
side note; all the cosmetic changes are making this hard to review. I have to carefully read each line to make sure it does not contain a semantic change. Could we possibly have a minimal change with no cosmetic changes and send a second PR that has the formatting changes? This kind of separation significantly reduces the review difficulty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally agree. removed formatting changes
package test_expect_failure.missing_direct_deps | ||
|
||
object A { | ||
def foo = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pretty wild scala style here. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:) Anything you want me to change?
cc @retronym who may be interested to see his code show up here. |
test this please (dear ci bot) |
bb3a1e2
to
08c7a7c
Compare
class DependencyAnalyzer(val global: Global) extends Plugin { | ||
|
||
val name = "dependency-analyzer" | ||
val description = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is no longer the description right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed
for (usedJar <- usedJars; | ||
usedJarPath = usedJar.path; | ||
target <- indirect.get(usedJarPath) | ||
if !direct.contains(usedJarPath)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this idiomatic? shouldn't the guard be in the previous line? maybe @dkomanov knows
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved up to the previous line
scala/scala.bzl
Outdated
|
||
if (hasattr(ctx.attr, 'enable_dependency_analyzer') | ||
and ctx.attr.enable_dependency_analyzer | ||
and hasattr(ctx.attr, 'dependency_analyzer_plugin')): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need this third check? Is there a situation where enable_dependency_analyzer
exists but dependency_analyzer_plugin
doesn't?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not needed. removed it.
scala/scala.bzl
Outdated
compiler_classpath = ":".join([j.path for j in all_jars]) | ||
direct_jars = ",".join([j.path for j in cjars]) | ||
|
||
valid_jar_paths = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does this mean? when is a jar path invalid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was added in order to pass non-related failing tests.
On retrospect, this was incorrect and there was a missing test on 'file' deps, e.g. @com_google_guava_guava_21_0//jar:file
.
added a test, and deleted these lines
scala/scala.bzl
Outdated
"manifest": "%{name}_MANIFEST.MF", | ||
}, | ||
"enable_dependency_analyzer": attr.bool(default=True, mandatory=False), | ||
"dependency_analyzer_plugin": attr.label(default=Label("@io_bazel_rules_scala//plugin/src/main:dependency_analyzer"), allow_files=_jar_filetype, mandatory=False), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be a private attribute? are actual users supposed to be able to configure this? I think not so it should probably be internal (_dependency_analyzer_plugin
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to internal
scala/scala.bzl
Outdated
outputs=library_outputs, | ||
) | ||
|
||
scala_library_for_plugin_bootstrapping = rule( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as much as I hate comments maybe one is needed here since the attribute trick might be hard to grasp at first and second look
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment added
|
||
|
||
String[] pluginParams; | ||
if (ops.enableDependencyAnalyzer) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extract method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extracted
scala/scala.bzl
Outdated
@@ -162,9 +162,31 @@ def _compile(ctx, cjars, dep_srcjars, buildijar): | |||
all_srcjars = set(srcjars + list(dep_srcjars)) | |||
# look for any plugins: | |||
plugins = _collect_plugin_paths(ctx.attr.plugins) | |||
|
|||
if (hasattr(ctx.attr, 'enable_dependency_analyzer') | |||
and ctx.attr.enable_dependency_analyzer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think this (the ability to turn off dependency analyzer) doesn't have a test, right?
even if this is covered by existing tests (which I'm not sure) there's definitely no test which encodes the feature, right? WDYT about having the next pair also add that test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The feature to turn off dependency analyzer has not been added yet. This if statement is used because compile
method is used by scala_binary
which does not support the plugin yet, and also the bootstrapping version of scala_library
. What test do you suggest can be added here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the test will be to use the command line flags and see that we don't have the dependency analyzer.
But we'll need to wait for 0.5.3 or 0.6.0 for that change.
If you can add an internal issue about it and we'll add it later on
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added issue to rules_scala #248
test_run.sh
Outdated
echo "'bazel build of scala_library with missing direct deps should have failed." | ||
exit 1 | ||
fi | ||
echo "$output" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this echo (the first one) helpful for debugging? not against it just trying to understand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. If there is an error, the output of the build will be shown
@natansil the build fails after you rebased from master. Did it pass locally? |
@ittaiz, no I merged from github directly. Not doing that again... :) |
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. |
9888a86
to
c72cb05
Compare
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored by someone other than the pull request submitter. We need to confirm that they're okay with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
as the other commiter, I'm OK with the commits that were done. |
@ittaiz, finished with the CR changes. please review them as well |
scala/scala.bzl
Outdated
@@ -382,6 +382,13 @@ def collect_srcjars(targets): | |||
srcjars += [target.srcjars.srcjar] | |||
return srcjars | |||
|
|||
def update_jars_to_labels(jars2labels, target, java_provider): | |||
for jar in (java_provider.compile_jars + java_provider.transitive_runtime_jars): | |||
if jar.path not in jars2labels: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess this is to eliminate d
showing up in both c
and d
right? if so maybe a named inner method, a comment or both would help clarify this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct, added several inner methods that hopefully convey the intent
scala/scala.bzl
Outdated
def update_jars_to_labels(jars2labels, target, java_provider): | ||
for jar in (java_provider.compile_jars + java_provider.transitive_runtime_jars): | ||
if jar.path not in jars2labels: | ||
if hasattr(target, "jars_to_labels") and jar.path in target.jars_to_labels: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you explain why this fixes the bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bug was that an incorrect target was given in the error message to the user. If we have A, B, and C, (A has B in deps
, B has C in deps
) than by the time A was built, the dependency information it receives was already aggregated and attached to the target B.
In order to retrieve information attached to target C, another provider (jars_to_labels
) was needed to be returned by scala_library
in order for targets such as C be exposed when building A
@natansil added two (hopefully last) comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is coming along great. One thing I am not seeing is how we ensure we use ijars for the indirect dependencies. If we use the runtime jars, we are going to cause a recompilation any time any aspect of any runtime dep changes. I don't think we want that as it will mean that we recompile everything downstream of all changes all the time. I think we need to be careful here.
scala/scala.bzl
Outdated
|
||
if (hasattr(ctx.attr, 'enable_dependency_analyzer') | ||
and ctx.attr.enable_dependency_analyzer): | ||
enable_dependency_analyzer = ctx.attr.enable_dependency_analyzer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't this always true here? Can we just write enable_dependency_analyzer = True
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed
scala/scala.bzl
Outdated
@@ -221,12 +245,13 @@ SourceJars: {srcjars} | |||
# _jdk added manually since _java doesn't currently setup runfiles | |||
# _scalac, as a java_binary, should already have it in its runfiles; however, | |||
# adding does ensure _java not orphaned if _scalac ever was not a java_binary | |||
ins = (list(cjars) + | |||
ins = (list(all_jars) + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is only all_jars
if dependency analysis is on, isn't it?
Also, in that case, if any of the runtime jars change at all, you will have to recompile. Won't this result in a lot of rebuilding? It seems like we want the ijars for the transitive dependencies, but not the runtime jars for the transitive dependencies. Is this actually what is happening, if so can we comment here to remind the reader?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed. passing only transitive compile_time ijars.
passing only direct deps if dependency analysis is off.
scala/scala.bzl
Outdated
"ijar": "%{name}_ijar.jar", | ||
"manifest": "%{name}_MANIFEST.MF", | ||
}, | ||
"enable_dependency_analyzer": attr.bool(default=True, mandatory=False), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this is here, it means we can't use it with scala_binary
, scala_test
or scala_macro_library
right. Any reason for the split?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We decided to start first with scala_library
, the pair continuing work this week will add this also to scala_binary
, scala_test
and scala_macro_library
, etc...
test this please (ci bot) |
@johnynek , please review after change of passing ijars instead of jars |
test this please (Ci bot) |
scala/scala.bzl
Outdated
compile_jars += java_provider.compile_jars | ||
transitive_cjars += java_provider.compile_jars |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we not access the transitive ijars with the java provider?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Java provider only exposes compile_jars
and transitive_runtime_jars
.
I've opened an issue for transitive_compile_time_jars
to be exposed
scala/scala.bzl
Outdated
|
||
runtime_dep_jars = _collect_jars(ctx.attr.runtime_deps + extra_runtime_deps) | ||
transitive_rjars += runtime_dep_jars.transitive_runtime_jars | ||
|
||
jars2labels.update(runtime_dep_jars.jars2labels) | ||
return struct(compile_jars = cjars, transitive_runtime_jars = transitive_rjars, jars2labels=jars2labels) | ||
return struct(compile_jars = cjars, transitive_runtime_jars = transitive_rjars, jars2labels=jars2labels, transitive_cjars = transitive_cjars) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for consistency with transitive_runtime_jars
should we call this transitive_compile_jars
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnynek WDYT about having transitive_cjars
be named compile_jars
because intent wise that's what they are. The jars which we're going to compile with. Implementation wise they are transitive but I'm not sure that's the focus.
And I'd rename the existing compile_jars
to be direct_jars
since that's the only reason they're still needed. For the plugin to know if the used jar is direct
or indirect
.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The java provider means compile_jars
to be direct though, no? I'd prefer to keep terminology with them, even if it is not ideal.
transitive_compile_jars
to me seems most consistent with bazel's naming. Do you disagree?
have you tested this internally on a real repo yet? |
Note: to get the biggest benefit of the compiler plugin that has been ported from the Classpath Shrinker, I recommend using Scala 2.11.11 that has improvements to the stub error messages that allows build tool authors to write bots to remove classpath entries automatically. This is what they are doing in Pants (EDITED). |
We have started the process today, I will update when we have results |
…yzer based on classpath shrinker
…all cjars to transitive_cjars in all cases
…dency changes internally
…d caveat on recompilations due to transitive dependencies changes
72ae9aa
to
41b8fa5
Compare
test this please |
Thank you so much @natansil! Appreciate the work along the long journey. |
We haven't seen appreciable overhead with the Strict Java Deps plugin. Would it be possible to put together a benchmark that demonstrates the slowdown you're seeing? Also, is it possible that the different with scala is caused by a longer classpath using strict deps than with manually curated deps? |
This PR is the first phase in implementing #235.
Description
It keeps the current end-user behavior of failing a build due to missing direct-deps.
This behavior is achieved in a different way. Instead of providing the Scala compiler only direct deps, it also provides it indirect-deps.
But an additional step is run by a scala compiler plugin called
dependency-analyzer
which is provided with build target information (direct jars, indirect jars and indirect target ).The plugin throws an error if it concludes that certain used jars are not directly dependent upon by the bazel target
A new feature is to provide the user with the information of which target label is missing in the build file.
For example:
Known limitations with the current PR:
Next planned work
Thanks to @nadavwe who contributed a lot to this feature