-
Notifications
You must be signed in to change notification settings - Fork 1.2k
"buck project" to copy generated Go code to vendor #1927
Conversation
Let's add support for modules in I think we can do that by adding a plugin extension point that would provide implementations of |
This is currently a blocking issue for us. As a compromise, can we keep Go in the core temporarily, and we are committed to move it back to features once you have that plugin extension point ready? JVM and Apple languages are still in the core anyway, and that is why project command is able to create IntelliJ and XCode projects. We don't like the idea of keeping a private fork for this PR, as it moves many files, and changes from upstream are likely to result in a painful conflict and merge process. It's relatively easier to Go out of core. |
Figured out a way to load Go extension in runtime. So now Go module is out of core again. Please take another look @styurin |
@@ -233,6 +251,75 @@ private TargetGraph getProjectGraphForIde( | |||
buckEventBus, cell, enableParserProfiling, executor, passedInTargets); | |||
} | |||
|
|||
@SuppressWarnings("unchecked") | |||
private ExitCode copyGeneratedGoSource(TargetGraphAndTargets targetGraphAndTargets) |
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 please add some comments on how it works and what assumptions we make.
@SuppressWarnings("unchecked") | ||
private ExitCode copyGeneratedGoSource(TargetGraphAndTargets targetGraphAndTargets) | ||
throws IOException, InterruptedException { | ||
HashMap<String, BuildTargetSourcePath> generatedPackages = new HashMap<>(); |
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.
s/HashMap/Map in the declaration
.collect( | ||
Collectors.toMap( | ||
src -> packageName.get(), src -> (BuildTargetSourcePath) src))); | ||
} |
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.
else fail the build? Are there any valid cases when go library may have no package name?
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 there are, but we don't. Figuring out a proper default package name will need to load another class from go module and use the same reflection trick to call its methods.
Optional<String> packageName = | ||
(Optional<String>) getPackageNameMethod.invoke(constructorArg); | ||
if (packageName.isPresent()) { | ||
generatedPackages.putAll( |
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 remove some line breaks from these chaining calls? (same relates to some other statements below)
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.
Those line breaks are added by Google Java formatter, in order to pass CI checks
@@ -304,6 +304,7 @@ private void copyGeneratedGoCode( | |||
DefaultSourcePathResolver.from(new SourcePathRuleFinder(result.getActionGraphBuilder())); | |||
for (BuildTargetSourcePath sourcePath : generatedPackages.keySet()) { | |||
Path desiredPath = vendorPath.resolve(generatedPackages.get(sourcePath)); | |||
fs.deleteRecursivelyIfExists(desiredPath); |
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 will not handle sub-packages well.
For example if we have:
github.com/foo/bar
github.com/foo/bar/A
and process the later first then contents of A will be removed while processing root package of the repo.
Let's make sure we cleanup only contents of the current 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.
@mkaczanowski has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
@@ -190,7 +206,10 @@ public ExitCode parseTargetsAndRunProjectGenerator(List<String> arguments) | |||
|
|||
return ExitCode.SUCCESS; | |||
} | |||
|
|||
ExitCode result = initGoWorkspace(targetGraphAndTargets); |
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.
So it will affect all the invocations of buck project
even if it's not related to Go?
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.
No, initGoWorkspace
only scans go_library rules. If there is no such rule in the workspace, it is a no-op.
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.
From the way it looks it should implemented as a separate command.
For which IDE this command is implemented? Is that just copying files?
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 has been tested for GoLand and Visual Studio Code. Yes, it is just copying files. This is sufficient for these IDEs to work. Go is more like "convention over configuration" style. So it has strong conventions about where packages should be put, rather than relying on configuration files to tell the IDEs and Go toolchain where to search for packages.
Path vendorPath; | ||
ProjectFilesystem fs = cell.getFilesystem(); | ||
if (fs.exists(Paths.get("src"))) { | ||
vendorPath = Paths.get("src", "vendor"); |
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's vendor?
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.
vendor
is a place to put third-party libraries. Go toolchain and IDEs is able to automatically pick up the Go packages in vendor directory. It is a good place to put Go packages that we don't want to put into source control.
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 do not have repository-specific details in Buck logic. You need to change it to smarter about detecting this location.
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 not really repository-specific. Vendor folder is a golang concept: https://golang.org/cmd/go/#hdr-Vendor_Directories
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.
Note that in contrast to java Go currently doesn't have ability to link sources/jars from random locations. There is a WIP workspaces feature on Google's side that should enable better integration of go toolchain with build tools like buck or bazel but it's not released yet and final ETA is not clear.
Currently we are in the situation when all generated or downloaded code would not be discoverable in the IDE. Putting it in vendor allows us to easily browse code and be able to run third-party tools not integrated with buck that are expecting valid GOPATH folder with all dependencies in place. Although can probably be more generic, with this change we are going from the state where buck project is barely useful for go to the state where it sets up a project with valid dependencies.
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.
Vendor directory can be anywhere under GOPATH/src, and Go toolchain automatically detects that. if (fs.exists(Paths.get("src")))
is just in case buck root is at GOPATH, i.e., the parent of src
directory. Of course, this logic would fail if buck root is in a higher directory than GOPATH. In that case, Buck has to search all directories under buck root for src
. It is expensive and non-deterministic (e.g., what if there are more than one src
?). We don't want to sacrifice the performance and make the logic more complicated to handle a scenario that doesn't exist for us. We will leave it for future users when they have a real use case.
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 don't really want to change Buck when there is a change in where these libs are located. You can put it in a configuration option and set it to whatever you need in your repository.
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.
Go toolchain and IDEs don't take configurations like that, unfortunately. Only packages in the main src directory and vendor
directories are recognized.
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.
Go toolchain and IDEs don't take configurations like that
But what if they change location? You would need to change this code to make it working, then deal with support of the different versions of these tools.
A configuration option makes it much easier to maintain. You can even use a configuration option with default value of src/vendor
when it's not present.
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 approach here is very flexible, not repository specific at all. If a project has its own vendor folder at GOPATH/src/foo/bar/vendor
, and the buck root is at GOPATH
, then the generated code will be copied into GOPATH/src/vendor
. If the buck root is at GOPATH/src/foo, the the generated code will be copied to GOPATH/src/foo/vendor
. All of these vendor folders are recognized by Go toolchain and IDEs at the same time.
Of course, if you want the vendor folder location to be further configurable by users, I can do that too.
Overall, I see this PR a temporary solution to unblock Go developers using Buck. For long term solution, there are discussions on Bazel side for over a year (1, 2). We may want to watch with Bazel community and see what they end up with. Whatever the long term solution ends up, I am happy to assist the migration.
If you have further concerns about this feature, I can also add a configuration that disable this feature by default.
I also notice that @mkaczanowski recently commit changes trying to support IDEs (great thanks to @mkaczanowski), but the performance is somehow quite bad (I need to dig a bit deeper to understand why), and I am not quite sure about symlink tree too, as people in Bazel run into problems with symlink trees (3)
if (classLoader != null) { | ||
goLibraryDescriptionArgClass = | ||
Class.forName( | ||
"com.facebook.buck.features.go.GoLibraryDescriptionArg", true, classLoader); |
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.
Could you wait with that? I'll try to migrate project command to a plugin model, but it can take some time.
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.
Do you see any difficulties with changing this in future?
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 really need this feature now. Since changes in this PR is very localized, it is easy to change whenever you migrate project command. I am happy to assist you for migrating this piece of code.
@mkaczanowski You imported this PR 4 days ago. Could you check why it is still not merged yet? |
Purge existing vendor package before copying genrated code
@linzhp has updated the pull request. Re-import the pull request |
@linzhp has updated the pull request. Re-import the pull request |
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.
@styurin has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
thanks @linzhp this is very useful for us as well! |
For most Go IDE/tools to work, every imported package has to be in some proper directories under GOPATH. However, Go code generated during a Buck build is not. This PR copies them to the vendor directory as part of "buck project --ide=intellij", although this change not only makes the IntelliJ/GoLand work, but also enable other IDEs and static check tools work with Go projects built with Buck.
In order for "buck project" command to identify Go targets and
IjProjectCommandHelper
to use Go related classes, I moved the Go module from features to core.@styurin @kageiit Please review