Skip to content

Commit

Permalink
[internal] Hook up check to Go (pantsbuild#13322)
Browse files Browse the repository at this point in the history
This lets you compile a package without needing to run tests or package a binary. 

Note that you can only directly compile a first-party package - to check if a third-party package is buildable, it must be a dependency of a first-party package. Works around pantsbuild#13175.

This does not yet have an optimal UX. It's overly chatty:

```
❯ ./pants check testprojects/src/go::
13:36:38.57 [INFO] Initializing scheduler...
13:36:39.20 [INFO] Scheduler initialized.
13:36:39.71 [ERROR] Completed: pants.backend.go.goals.check.check_go - go failed (exit code 1).
Partition #1 - github.com/pantsbuild/pants/testprojects/src/go/pants_test:
./testprojects/src/go/pants_test/foo.go:3:1: syntax error: non-declaration statement outside function body

Partition #2 - github.com/pantsbuild/pants/testprojects/src/go/pants_test/bar:



𐄂 go failed.
```

We also should be streaming the result of each compilation as it happens, which has an added benefit of that output happening when compilation happens as a side effect of `run`, `package`, and `test`. Those fixes will be in a followup.

[ci skip-rust]
[ci skip-build-wheels]
  • Loading branch information
Eric-Arellano authored Oct 22, 2021
1 parent 5a2207a commit 16dd4e0
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/python/pants/backend/experimental/go/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from pants.backend.go import target_type_rules
from pants.backend.go.goals import package_binary, run_binary, tailor, test
from pants.backend.go.goals import check, package_binary, run_binary, tailor, test
from pants.backend.go.lint import fmt
from pants.backend.go.lint.gofmt import skip_field as gofmt_skip_field
from pants.backend.go.lint.gofmt.rules import rules as gofmt_rules
Expand Down Expand Up @@ -34,6 +34,7 @@ def rules():
return [
*assembly.rules(),
*build_pkg.rules(),
*check.rules(),
*third_party_pkg.rules(),
*golang.rules(),
*import_analysis.rules(),
Expand Down
76 changes: 76 additions & 0 deletions src/python/pants/backend/go/goals/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from dataclasses import dataclass
from typing import cast

from pants.backend.go.target_types import GoFirstPartyPackageSourcesField
from pants.backend.go.util_rules.build_pkg import (
BuildGoPackageRequest,
BuildGoPackageTargetRequest,
FallibleBuildGoPackageRequest,
FallibleBuiltGoPackage,
)
from pants.core.goals.check import CheckRequest, CheckResult, CheckResults
from pants.engine.rules import Get, MultiGet, collect_rules, rule
from pants.engine.target import FieldSet
from pants.engine.unions import UnionRule


@dataclass(frozen=True)
class GoCheckFieldSet(FieldSet):
required_fields = (GoFirstPartyPackageSourcesField,)

sources: GoFirstPartyPackageSourcesField


class GoCheckRequest(CheckRequest):
field_set_type = GoCheckFieldSet


@rule
async def check_go(request: GoCheckRequest) -> CheckResults:
build_requests = await MultiGet(
Get(FallibleBuildGoPackageRequest, BuildGoPackageTargetRequest(field_set.address))
for field_set in request.field_sets
)
invalid_requests = []
valid_requests = []
for fallible_request in build_requests:
if fallible_request.request is None:
invalid_requests.append(fallible_request)
else:
valid_requests.append(fallible_request.request)

build_results = await MultiGet(
Get(FallibleBuiltGoPackage, BuildGoPackageRequest, request) for request in valid_requests
)

# TODO: Update `build_pkg.py` to use streaming workunits to log compilation results, which has
# the benefit of other contexts like `test.py` using it. Switch this to only preserve the
# exit code.
check_results = [
*(
CheckResult(
result.exit_code,
"",
cast(str, result.stderr),
partition_description=result.import_path,
)
for result in invalid_requests
),
*(
CheckResult(
result.exit_code,
result.stdout or "",
result.stderr or "",
partition_description=result.import_path,
)
for result in build_results
),
]
return CheckResults(check_results, checker_name="go")


def rules():
return [*collect_rules(), UnionRule(CheckRequest, GoCheckRequest)]
85 changes: 85 additions & 0 deletions src/python/pants/backend/go/goals/check_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

from textwrap import dedent

import pytest

from pants.backend.go import target_type_rules
from pants.backend.go.goals import check
from pants.backend.go.goals.check import GoCheckFieldSet, GoCheckRequest
from pants.backend.go.target_types import GoModTarget
from pants.backend.go.util_rules import (
assembly,
build_pkg,
first_party_pkg,
go_mod,
import_analysis,
sdk,
third_party_pkg,
)
from pants.core.goals.check import CheckResult, CheckResults
from pants.engine.addresses import Address
from pants.testutil.rule_runner import QueryRule, RuleRunner


@pytest.fixture
def rule_runner() -> RuleRunner:
rule_runner = RuleRunner(
rules=[
*check.rules(),
*sdk.rules(),
*assembly.rules(),
*build_pkg.rules(),
*import_analysis.rules(),
*go_mod.rules(),
*first_party_pkg.rules(),
*third_party_pkg.rules(),
*target_type_rules.rules(),
QueryRule(CheckResults, [GoCheckRequest]),
],
target_types=[GoModTarget],
)
rule_runner.set_options([], env_inherit={"PATH"})
return rule_runner


def test_check(rule_runner: RuleRunner) -> None:
rule_runner.write_files(
{
"go.mod": dedent(
"""\
module example.com/greeter
go 1.17
"""
),
"bad/f.go": "invalid!!!",
"good/f.go": dedent(
"""\
package greeter
import "fmt"
func Hello() {
fmt.Println("Hello world!")
}
"""
),
"BUILD": "go_mod(name='mod')",
}
)
targets = [
rule_runner.get_target(Address("", target_name="mod", generated_name="./bad")),
rule_runner.get_target(Address("", target_name="mod", generated_name="./good")),
]
results = rule_runner.request(
CheckResults, [GoCheckRequest(GoCheckFieldSet.create(tgt) for tgt in targets)]
).results
assert set(results) == {
CheckResult(0, "", "", "example.com/greeter/good"),
CheckResult(
1, "", "bad/f.go:1:1: expected 'package', found invalid\n", "example.com/greeter/bad"
),
}

0 comments on commit 16dd4e0

Please sign in to comment.