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

Added example using Buck with Visual Studio Code on Windows #517

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions examples/visual_studio/.buckconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[repositories]
root = .
prelude = prelude
toolchains = toolchains
none = none

[repository_aliases]
config = prelude
fbcode = none
fbsource = none
buck = none

[parser]
target_platform_detector_spec = target:root//...->toolchains//:default

[build]
execution_platforms = prelude//platforms:default
Empty file.
4 changes: 4 additions & 0 deletions examples/visual_studio/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/buck-out
/compile_commands.json
/install
/.cache
3 changes: 3 additions & 0 deletions examples/visual_studio/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["llvm-vs-code-extensions.vscode-clangd", "vadimcn.vscode-lldb"]
}
13 changes: 13 additions & 0 deletions examples/visual_studio/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug main.exe",
"preLaunchTask": "Build and create compilation database",
"program": "${workspaceFolder}/install/main.exe",
"cwd": "${workspaceFolder}/install"
}
]
}
4 changes: 4 additions & 0 deletions examples/visual_studio/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"C_Cpp.intelliSenseEngine": "disabled",
"search.followSymlinks": false
}
55 changes: 55 additions & 0 deletions examples/visual_studio/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build Main",
"type": "shell",
"command": "buck2",
"args": [
"build",
"--console",
"simple",
"--no-interactive-console",
"--fail-fast",
"--show-full-output",
":main",
"|",
"powershell",
"-ExecutionPolicy",
"Bypass",
"-File",
"buck2_utils/install.ps1",
"install"
]
},
{
"label": "Create compilation database",
"type": "shell",
"command": "buck2",
"args": [
"bxl",
"buck2_utils/create_compile_commands.bxl:gen_compilation_database",
"--",
"--directory",
"${workspaceFolder}",
"|",
"powershell",
"-ExecutionPolicy",
"Bypass",
"-File",
"buck2_utils/copy.ps1"
]
},
{
"label": "Build and create compilation database",
"dependsOn": [
"Create compilation database",
"Build Main"
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
15 changes: 15 additions & 0 deletions examples/visual_studio/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cxx_binary(
name = "main",
srcs = ["main.c"],
link_style = "shared",
deps = [":print"],
resources = [":print[shared]"]
)

cxx_library(
name = "print",
srcs = ["library.cpp"],
exported_headers = glob(["**/*.hpp"]),
preprocessor_flags = ["-DLIBRARY_EXPORT"],
visibility = ["PUBLIC"],
)
8 changes: 8 additions & 0 deletions examples/visual_studio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## An example showing how Buck2 can be used in Visual Studio Code on Windows

After completing the setup, below, click F5 to run with the debugger attached. You can also use Ctrl + Shift + B to build. By default it compiles with the "debug" configuration. To compile in release pass "-c release" to Buck2's build command.

## Setup

Run `buck2 init --git`.
Open this folder in Visual Studio Code and install the recommended extensions.
16 changes: 16 additions & 0 deletions examples/visual_studio/buck2_utils/configuration/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
constraint_setting(
name = "configuration",
visibility = ["PUBLIC"],
)

constraint_value(
name = "debug",
constraint_setting = ":configuration",
visibility = ["PUBLIC"],
)

constraint_value(
name = "release",
constraint_setting = ":configuration",
visibility = ["PUBLIC"],
)
5 changes: 5 additions & 0 deletions examples/visual_studio/buck2_utils/copy.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
param(
[parameter(Mandatory=$true)] [String] $FilePath
)

New-Item -ItemType HardLink -Force -Path compile_commands.json -Target $FilePath.Trim() | Out-Null
133 changes: 133 additions & 0 deletions examples/visual_studio/buck2_utils/create_compile_commands.bxl
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
load("@prelude//:paths.bzl", "paths")

def group_by_category(actions):
groups = {}
for a in actions:
category = a.attrs.category.value()
if category in groups:
groups[category].append(a)
else:
groups[category] = [a]
return groups

def create_compilation_database_entry(directory, buildfile_folder, argsfiles, action):
# the "cmd" attribute looks like a list. For example, it might be something like:
# cmd = [cl.exe, -c, foo.cpp]
# but in reality this is quite literally the string "[cl.exe, -c, foo.cpp]". We need
# it as an actual list, so we have to employ this unfortunate hack of stripping the brackets,
# splitting on comma operator, and then stripping spaces from each entry to make an actual
# list. Hopefully this doesn't fail in any weird edge cases, but it seems to be the best we
# can do until buck2 upstream actually stores a Starlark list as the value of this attribute.
cmd = action.attrs.cmd.value()
cmd = cmd.strip("[]").split(',')
cmd = list(map(lambda x: x.strip(), cmd))

compiler = cmd[0].replace(".bat", ".exe")

# Find the argsfile. On Windows it might contain backslashes, so convert those to forward
# slashes. Hopefully we don't have any filenames with actual backslashes in them. Then
# strip off the directory name so we just have the filename of the argsfile.
argsfile = filter(lambda x: x.startswith("@"), cmd)[0][1:]
identifier = paths.basename(argsfile.replace("\\", "/"))

# Get the path of the source file relative to the project root by appending the identifier
# to the directory of the BUCK file.
project_rel_path = paths.join(buildfile_folder, action.attrs.identifier.value())

# Using the previously computed list of argfiles referenced by this target, find the one
# that this particular action references.
arguments = []
if identifier in argsfiles:
arguments = argsfiles[identifier]

# Now build the compilation database entry
entry = {
"file": project_rel_path,
"directory": directory,
"arguments": [compiler] + arguments
}
return entry

def gen_compilation_database_impl(ctx):
# Generate compilation database for all targets unless user requests a more narrow set
target_filter = ctx.cli_args.targets or '...'
targets = ctx.configured_targets(target_filter, target_platform=ctx.cli_args.platform)
aquery = ctx.aquery()

entries = {}
for target in targets:
target_actions = list(aquery.all_actions(target.label))

# The individual compilation actions only identify the source files relative to where
# the BUCK file is. So to build a path relative to the root of the project, we need
# to get the BUCK file path and append the source file path to it.
buildfile_path = ctx.fs.project_rel_path(target.buildfile_path)
buildfile_folder = paths.dirname(buildfile_path)

# In order to generate a compilation database entry for a file, we need this target
# to satisfy two conditions:
# 1. It has compilations
# 2. It writes an argsfile.
#
# The second requirement is necessary because the argsfile is where buck2 includes
# important command line arguments such as preprocessor definitions and include
# paths. If both conditions are not satisfied, we should skip this target because
# either it doesn't have source files anyway, or we don't understand how to write
# compilation database entries for them.
actions_by_category = group_by_category(target_actions)

if not "write" in actions_by_category:
continue
if not "cxx_compile" in actions_by_category:
continue

argsfiles = {}

# There are lots of kinds of "write" actions, but we are interested specifically in argsfile
# write actions. This is because the source file compilation actions will reference them,
# so for each source file we need to map it back to the argsfile that its command line references
# so we can write those arguments to the compilation database entry. Build this mapping here.
for write_action in actions_by_category["write"]:
identifier = write_action.attrs.identifier.value()
if identifier.endswith(".argsfile"):
# The content of the argsfile is a newline separated list. We need a Starlark list. So
# split on newline.
arguments = write_action.attrs.contents.value()
arguments = arguments.split("\n")

# For whatever reason, there are a lot of unnecessary quotes and double slashes. Probably
# something to do with the nature of the tools requiring shell-escaped values. Since we're
# storing this in a JSON array and the tool that processes the compilation database does its
# own escaping of these values, we can undo all of this. Convert double backslash to
# single forward slash, and remove quotes at the beginning and end of entries.
arguments = list(map(lambda x: x.strip('"').replace("\\\\", "/"), arguments))
argsfiles[identifier] = arguments

# Now walk each compilation action and generate a compilation database entry for it.
for action in actions_by_category["cxx_compile"]:
entry = create_compilation_database_entry(ctx.cli_args.directory, buildfile_folder, argsfiles, action)
is_pic = entry["file"].endswith(" (pic)")
file_name = entry["file"].removesuffix(" (pic)")
entry["file"] = file_name
entry["is_pic"] = is_pic
existing_entry = entries.get(file_name)
if existing_entry == None or existing_entry["is_pic"]:
entries[file_name] = entry

for entry in entries.values():
entry.pop("is_pic")

actions = ctx.bxl_actions(target_platform = ctx.cli_args.platform).actions
db_artifact = actions.write_json("compile_commands.json", entries.values())
db_artifact_ensured = ctx.output.ensure(db_artifact)

ctx.output.print(db_artifact_ensured)

gen_compilation_database = bxl_main(
impl = gen_compilation_database_impl,
cli_args = {
"directory": cli_args.string(),
"targets": cli_args.option(cli_args.target_expr()),
"platform": cli_args.option(cli_args.target_label())
}
)
69 changes: 69 additions & 0 deletions examples/visual_studio/buck2_utils/install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
param(
[parameter(Mandatory=$true)] [String] $InstallDirectory,
[parameter(Mandatory=$true,ValueFromPipeline=$true)] [String] $Buck2Output
)

begin
{
$ErrorActionPreference = "Stop"

function Copy-Directory
{
param(
[parameter(Mandatory=$true)] [String] $SourcePath,
[parameter(Mandatory=$true)] [String] $TargetPath
)
robocopy /MIR "$SourcePath" "$TargetPath" | Out-Null
}

function Copy-File
{
param(
[parameter(Mandatory=$true)] [String] $SourcePath,
[parameter(Mandatory=$true)] [String] $TargetPath
)
Copy-Item $SourcePath -Destination $TargetPath
}
}
process
{
$OutputSplit = $input.Split(" ")
$TargetPath = $OutputSplit[0].Trim()
$ExecutablePath = $OutputSplit[1].Trim()
$ResourcesPath = $ExecutablePath + ".resources.json"

$CopySource = $ExecutablePath
$CopyTarget = Join-Path -Path $InstallDirectory -ChildPath (Split-Path -Leaf $ExecutablePath)
Copy-File $CopySource $CopyTarget

try
{
$ResourcesContent = Get-Content $ResourcesPath -ErrorAction Stop | ConvertFrom-Json
$HasResources = $true
}
catch [System.Management.Automation.ItemNotFoundException]
{
$ResourcesContent = @()
$HasResources = $false
}
if($HasResources)
{
foreach($ResourceProperty in $ResourcesContent.PSObject.Properties)
{
$ResourceTargetPath = $ResourceProperty.Name
$ResourceSourcePath = $ResourceProperty.Value
$CopySource = Join-Path -Path (Split-Path -Parent $ExecutablePath) -ChildPath $ResourceSourcePath
$CopyTarget = Join-Path -Path $InstallDirectory -ChildPath $ResourceTargetPath

$GetItemResult = Get-Item $CopySource
if($GetItemResult.PSIsContainer)
{
Copy-Directory $CopySource $CopyTarget
}
else
{
Copy-File $CopySource $CopyTarget
}
}
}
}
16 changes: 16 additions & 0 deletions examples/visual_studio/library.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under both the MIT license found in the
* LICENSE-MIT file in the root directory of this source tree and the Apache
* License, Version 2.0 found in the LICENSE-APACHE file in the root directory
* of this source tree.
*/

#include "library.hpp"

#include <iostream>

void print_hello() {
std::cout << "Hello from a C++ Buck2 program!" << std::endl;
}
25 changes: 25 additions & 0 deletions examples/visual_studio/library.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under both the MIT license found in the
* LICENSE-MIT file in the root directory of this source tree and the Apache
* License, Version 2.0 found in the LICENSE-APACHE file in the root directory
* of this source tree.
*/

#ifdef _WIN32
#ifdef LIBRARY_EXPORT
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
#endif

#ifdef __cplusplus
extern "C"
{
#endif
DLL_API void print_hello();
#ifdef __cplusplus
}
#endif
Loading
Loading