Skip to content

Commit

Permalink
feat: go-use plugin to handle go workspaces (#864)
Browse files Browse the repository at this point in the history
Signed-off-by: Sergio Schvezov <[email protected]>
Co-authored-by: Tiago Nobrega <[email protected]>
  • Loading branch information
sergiusens and tigarmo authored Oct 18, 2024
1 parent 9d01009 commit 57cf26f
Show file tree
Hide file tree
Showing 15 changed files with 592 additions and 6 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ jobs:
echo "::group::apt install"
sudo apt install -y ninja-build cmake scons \
autoconf automake autopoint gcc git gperf help2man libtool texinfo
# Remove newer go and install regular version for 20.04
sudo apt install -y golang
# Install go from the Snap Store
go_job=$(sudo snap install --no-wait --classic go)
# Install RPM dependencies for RPM tests
sudo apt install rpm
# Install poetry. From pipx on focal, from apt on newer systems.
Expand All @@ -96,6 +96,7 @@ jobs:
else
sudo apt-get install -y python3-poetry
fi
snap watch $go_job
# Ensure we don't have dotnet installed, to properly test dotnet-deps
# Based on https://github.com/actions/runner-images/blob/main/images/linux/scripts/installers/dotnetcore-sdk.sh
sudo apt remove -y dotnet-* || true
Expand Down Expand Up @@ -159,11 +160,12 @@ jobs:
echo "::group::snap install"
sudo snap install --no-wait core20
sudo snap install chisel --channel latest/candidate --no-wait
sudo snap install go --classic --no-wait
echo "::endgroup::"
echo "::group::apt install"
sudo apt install -y ninja-build cmake scons qt5-qmake p7zip \
autoconf automake autopoint gcc git gperf help2man libtool texinfo \
curl findutils pkg-config golang rpm \
curl findutils pkg-config rpm \
findutils python3-dev python3-venv
echo "::endgroup::"
echo "::group::dotnet removal"
Expand Down
11 changes: 10 additions & 1 deletion craft_parts/plugins/go_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,21 @@ def get_build_commands(self) -> list[str]:
"""Return a list of commands to run during the build step."""
options = cast(GoPluginProperties, self._options)

# Matches go-use plugin expectation.
workspace_dir = self._part_info.project_info.dirs.parts_dir
workspace = workspace_dir / "go.work"

if workspace.exists():
init_cmd = f"go work use {self._part_info.part_build_dir}"
else:
init_cmd = "go mod download all"

tags = f"-tags={','.join(options.go_buildtags)}" if options.go_buildtags else ""

generate_cmds: list[str] = [f"go generate {cmd}" for cmd in options.go_generate]

return [
"go mod download all",
init_cmd,
*generate_cmds,
f'go install -p "{self._part_info.parallel_build_count}" {tags} ./...',
]
104 changes: 104 additions & 0 deletions craft_parts/plugins/go_use_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2020-2021,2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""The Go Use plugin."""

import logging
import subprocess
from typing import Literal

from overrides import override

from craft_parts import errors

from .base import Plugin
from .go_plugin import GoPluginEnvironmentValidator
from .properties import PluginProperties

logger = logging.getLogger(__name__)


class GoUsePluginProperties(PluginProperties, frozen=True):
"""The part properties used by the Go Use plugin."""

plugin: Literal["go-use"] = "go-use"

# part properties required by the plugin
source: str # pyright: ignore[reportGeneralTypeIssues]


class GoUsePlugin(Plugin):
"""A plugin to setup the source into a go workspace.
The go plugin requires a go compiler installed on your system. This can
be achieved by adding the appropriate golang package to ``build-packages``,
or to have it installed or built in a different part. In this case, the
name of the part supplying the go compiler must be "go".
"""

properties_class = GoUsePluginProperties
validator_class = GoPluginEnvironmentValidator

@classmethod
def get_out_of_source_build(cls) -> bool:
"""Return whether the plugin performs out-of-source-tree builds."""
return True

@override
def get_build_snaps(self) -> set[str]:
"""Return a set of required snaps to install in the build environment."""
return set()

@override
def get_build_packages(self) -> set[str]:
"""Return a set of required packages to install in the build environment."""
return set()

@override
def get_build_environment(self) -> dict[str, str]:
"""Return a dictionary with the environment to use in the build step."""
return {}

@override
def get_build_commands(self) -> list[str]:
"""Return a list of commands to run during the build step."""
# Set the go workspace directory to live at the root of all parts.
workspace_dir = self._part_info.project_info.dirs.parts_dir
workspace = workspace_dir / "go.work"

# We do not want this implementation detail exposed in the run script
if not workspace.exists():
logger.debug(f"Init go workspace at {workspace}")
try:
subprocess.run(
["go", "work", "init"],
capture_output=True,
check=True,
cwd=workspace_dir,
)
except subprocess.CalledProcessError as call_error:
logger.debug(
f"Workspace init failed {call_error!r} "
f"stdout: {call_error.stdout!r} "
f"stderr: {call_error.stderr}"
)
raise errors.PluginBuildError(
part_name=self._part_info.part_name, plugin_name="go-use"
)

return [
f"go work use {self._part_info.part_src_dir}",
]
2 changes: 2 additions & 0 deletions craft_parts/plugins/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from .dotnet_plugin import DotnetPlugin
from .dump_plugin import DumpPlugin
from .go_plugin import GoPlugin
from .go_use_plugin import GoUsePlugin
from .make_plugin import MakePlugin
from .maven_plugin import MavenPlugin
from .meson_plugin import MesonPlugin
Expand Down Expand Up @@ -54,6 +55,7 @@
"dotnet": DotnetPlugin,
"dump": DumpPlugin,
"go": GoPlugin,
"go-use": GoUsePlugin,
"make": MakePlugin,
"maven": MavenPlugin,
"meson": MesonPlugin,
Expand Down
2 changes: 2 additions & 0 deletions docs/common/craft-parts/craft-parts.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ GitSourceModel
GoPlugin
GoPluginEnvironmentValidator
GoPluginProperties
GoUsePlugin
GoUsePluginProperties
HttpRequestError
IncompatibleSourceOptions
InvalidAction
Expand Down
8 changes: 6 additions & 2 deletions docs/common/craft-parts/reference/plugins/go_plugin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ How it works

During the build step the plugin performs the following actions:

* Call ``go mod download all`` to find and download all necessary modules;
* If a `go workspace`_ has been setup by use of the :ref:`go-use <craft_parts_go_use_plugin>`
plugin,
call ``go work use <build-dir>`` to add the source for the part to the workspace;
* If not operating in the context of a `go workspace`_, call ``go mod download all``
to find and download all necessary modules;
* Call ``go generate <item>`` for each item in ``go-generate``;
* Call ``go install ./...``, passing the items in ``go-buildtags`` through the
``--tags`` parameter.
Expand Down Expand Up @@ -94,4 +98,4 @@ The following snippet declares a part using the ``go`` plugin. It uses the stabl
.. _Build tags: https://pkg.go.dev/cmd/go#hdr-Build_constraints
.. _Go: https://go.dev/
.. _go generate: https://go.dev/blog/generate

.. _go workspace: https://go.dev/blog/get-familiar-with-workspaces
71 changes: 71 additions & 0 deletions docs/common/craft-parts/reference/plugins/go_use_plugin.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.. _craft_parts_go_use_plugin:

Go Use plugin
=============

The Go Use plugin allows for setting up a `go workspace`_ for `Go`_ modules. It is
a companion plugin meant to be used with the :ref:`Go plugin <craft_parts_go_plugin>`.
Use of this plugin sets up ``go.work`` and affects all parts.

Keywords
--------

There are no additional keywords to the the common :ref:`plugin <part-properties-plugin>`
and :ref:`sources <part-properties-sources>` keywords.

.. _go-use-details-begin:

Dependencies
------------

The Go plugin needs the ``go`` executable to build Go programs but does not
provision it by itself, to allow flexibility in the choice of compiler version.

Common means of providing ``go`` are:

* The ``golang`` Ubuntu package, declared as a ``build-package``.
* The ``go`` snap, declared as a ``build-snap`` from the desired channel.

Another alternative is to define another part with the name ``go-deps``, and
declare that the part using the ``go`` plugin comes :ref:`after <after>` the
``go-deps`` part. In this case, the plugin will assume that this new part will
stage the ``go`` executable to be used in the build step. This can be useful,
for example, in cases where a specific, unreleased version of ``go`` is desired
but unavailable as a snap or an Ubuntu package.

.. _go-use-details-end:

How it works
------------

During the build step the plugin performs the following actions:

* Setup a `go workspace`_ if ``go.work`` has not been setup;
* Call ``go work use <source-dir>`` to add the source for the part to the workspace;

Examples
--------

The following snippet declares a parts named ``go-flags`` using the ``go-use`` plugin and
a ``hello`` part that declares this ``go-flags``` in its ``go.mod`` using the ``go`` plugin.
Correct ordering is achieved with the use of the ``after`` keyword in the ``hello`` part.

.. code-block:: yaml
parts:
go-flags:
source: https://github.com/jessevdk/go-flags.git
plugin: go-use
hello:
build-snaps:
- go/1.22/stable
plugin: go
source: .
after:
- go-flags
.. _Build tags: https://pkg.go.dev/cmd/go#hdr-Build_constraints
.. _Go: https://go.dev/
.. _go generate: https://go.dev/blog/generate
.. _go workspace: https://go.dev/blog/get-familiar-with-workspaces
1 change: 1 addition & 0 deletions docs/reference/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ lifecycle.
/common/craft-parts/reference/plugins/cmake_plugin.rst
/common/craft-parts/reference/plugins/dump_plugin.rst
/common/craft-parts/reference/plugins/go_plugin.rst
/common/craft-parts/reference/plugins/go_use_plugin.rst
/common/craft-parts/reference/plugins/make_plugin.rst
/common/craft-parts/reference/plugins/maven_plugin.rst
/common/craft-parts/reference/plugins/meson_plugin.rst
Expand Down
38 changes: 38 additions & 0 deletions tests/integration/plugins/test_go.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,41 @@ def test_go_generate(new_dir, partitions):
output = subprocess.check_output([str(binary)], text=True)
# This is the expected output that "gen/generator.go" sets in "main.go"
assert output == "This is a generated line\n"


def test_go_workspace_use(new_dir, partitions):
source_location = Path(__file__).parent / "test_go_workspace"

parts_yaml = textwrap.dedent(
f"""
parts:
go-flags:
source: https://github.com/jessevdk/go-flags.git
plugin: go-use
hello:
after:
- go-flags
plugin: go
source: {source_location}
build-environment:
- GO111MODULE: "on"
"""
)
parts = yaml.safe_load(parts_yaml)
lf = LifecycleManager(
parts,
application_name="test_go",
cache_dir=new_dir,
work_dir=new_dir,
partitions=partitions,
)
actions = lf.plan(Step.PRIME)

with lf.action_executor() as ctx:
ctx.execute(actions)

binary = Path(lf.project_info.prime_dir, "bin", "workspace")
assert binary.is_file()

output = subprocess.check_output([str(binary), "--say=hello"], text=True)
assert output == "hello\n"
Loading

0 comments on commit 57cf26f

Please sign in to comment.