Skip to content

Commit

Permalink
ci: fix release pipleine to automatically generate manifests (#90)
Browse files Browse the repository at this point in the history
* removed manifest files

* removed unneeded fields from plugin.json

* updated manifest tool

* autogenerate manifests

* readme about how version is discovered

* run apply on test and coverage too

* revert test file

* fixed webapp manifest import
  • Loading branch information
fmartingr authored Jul 31, 2024
1 parent 5a0a89b commit a019ed7
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 45 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ dist*
.DS_Store
.npminstall
webapp/node_modules
webapp/src/manifest.ts
server/manifest.go
18 changes: 11 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ default: all

# Verify environment, and define PLUGIN_ID, PLUGIN_VERSION, HAS_SERVER and HAS_WEBAPP as needed.
include build/setup.mk
include build/legacy.mk

BUNDLE_NAME ?= $(PLUGIN_ID)-$(PLUGIN_VERSION).tar.gz

Expand Down Expand Up @@ -156,9 +155,14 @@ major-rc: ## to bump major release candidate version (semver)
.PHONY: all
all: check-style test dist

## Propagates plugin manifest information into the server/ and webapp/ folders.
.PHONY: apply
apply:
./build/bin/manifest apply

## Runs golangci-lint and eslint.
.PHONY: check-style
check-style: webapp/node_modules
check-style: webapp/node_modules apply
@echo Checking for style guide compliance

ifneq ($(HAS_WEBAPP),)
Expand Down Expand Up @@ -260,7 +264,7 @@ validate-go-version: ## Validates the installed version of go against Mattermost
bundle:
rm -rf dist/
mkdir -p dist/$(PLUGIN_ID)
cp $(MANIFEST_FILE) dist/$(PLUGIN_ID)/plugin.json
./build/bin/manifest dist
ifneq ($(wildcard $(ASSETS_DIR)/.),)
cp -r $(ASSETS_DIR) dist/$(PLUGIN_ID)/
endif
Expand All @@ -281,7 +285,7 @@ endif

## Builds and bundles the plugin.
.PHONY: dist
dist: server webapp bundle
dist: apply server webapp bundle

## Builds and installs the plugin to a server.
.PHONY: deploy
Expand All @@ -290,7 +294,7 @@ deploy: dist

## Builds and installs the plugin to a server, updating the webapp automatically when changed.
.PHONY: watch
watch: server bundle
watch: apply server bundle
ifeq ($(MM_DEBUG),)
cd webapp && $(NPM) run build:watch
else
Expand Down Expand Up @@ -344,7 +348,7 @@ detach: setup-attach

## Runs any lints and unit tests defined for the server and webapp, if they exist.
.PHONY: test
test: webapp/node_modules
test: apply webapp/node_modules
ifneq ($(HAS_SERVER),)
$(GO) test -v $(GO_TEST_FLAGS) $(GO_PACKAGES)
endif
Expand All @@ -354,7 +358,7 @@ endif

## Creates a coverage report for the server code.
.PHONY: coverage
coverage: webapp/node_modules
coverage: apply webapp/node_modules
ifneq ($(HAS_SERVER),)
$(GO) test $(GO_TEST_FLAGS) -coverprofile=server/coverage.txt $(GO_PACKAGES)
$(GO) tool cover -html=server/coverage.txt
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ To trigger a release, follow these steps:
```
This will release a major release candidate.
### Releasing new versions
The version of a plugin is determined at compile time, automatically populating a `version` field in the [plugin manifest](plugin.json):
* If the current commit matches a tag, the version will match after stripping any leading `v`, e.g. `1.3.1`.
* Otherwise, the version will combine the nearest tag with `git rev-parse --short HEAD`, e.g. `1.3.1+d06e53e1`.
* If there is no version tag, an empty version will be combined with the short hash, e.g. `0.0.0+76081421`.
To disable this behaviour, manually populate and maintain the `version` field.
## Acknowledgments
* [Hossein Ahmadian-Yazdi](https://github.com/hahmadia) for the previous version of the Google Calendar plugin:
Expand Down
3 changes: 0 additions & 3 deletions build/legacy.mk

This file was deleted.

147 changes: 138 additions & 9 deletions build/manifest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,50 @@ import (
"encoding/json"
"fmt"
"os"
"strings"

"github.com/mattermost/mattermost/server/public/model"
"github.com/pkg/errors"
)

const pluginIDGoFileTemplate = `// This file is automatically generated. Do not modify it manually.
package main
import (
"encoding/json"
"strings"
"github.com/mattermost/mattermost/server/public/model"
)
var manifest *model.Manifest
const manifestStr = ` + "`" + `
%s
` + "`" + `
func init() {
_ = json.NewDecoder(strings.NewReader(manifestStr)).Decode(&manifest)
}
`

const pluginIDJSFileTemplate = `// This file is automatically generated. Do not modify it manually.
const manifest = JSON.parse(` + "`" + `
%s
` + "`" + `);
export default manifest;
`

// These build-time vars are read from shell commands and populated in ../setup.mk
var (
BuildHashShort string
BuildTagLatest string
BuildTagCurrent string
)

func main() {
if len(os.Args) <= 1 {
panic("no cmd specified")
Expand Down Expand Up @@ -37,22 +76,26 @@ func main() {
fmt.Printf("true")
}

case "apply":
if err := applyManifest(manifest); err != nil {
panic("failed to apply manifest: " + err.Error())
}

case "dist":
if err := distManifest(manifest); err != nil {
panic("failed to write manifest to dist directory: " + err.Error())
}

default:
panic("unrecognized command: " + cmd)
}
}

func findManifest() (*model.Manifest, error) {
manifestFilePath := os.Getenv("MANIFEST_FILE")

if manifestFilePath == "" {
var err error
_, manifestFilePath, err = model.FindManifest(".")
if err != nil {
return nil, errors.Wrap(err, "failed to find manifest in current working directory")
}
_, manifestFilePath, err := model.FindManifest(".")
if err != nil {
return nil, errors.Wrap(err, "failed to find manifest in current working directory")
}

manifestFile, err := os.Open(manifestFilePath)
if err != nil {
return nil, errors.Wrapf(err, "failed to open %s", manifestFilePath)
Expand All @@ -68,6 +111,32 @@ func findManifest() (*model.Manifest, error) {
return nil, errors.Wrap(err, "failed to parse manifest")
}

// If no version is listed in the manifest, generate one based on the state of the current
// commit, and use the first version we find (to prevent causing errors)
if manifest.Version == "" {
var version string
tags := strings.Fields(BuildTagCurrent)
for _, t := range tags {
if strings.HasPrefix(t, "v") {
version = t
break
}
}
if version == "" {
if BuildTagLatest != "" {
version = BuildTagLatest + "+" + BuildHashShort
} else {
version = "v0.0.0+" + BuildHashShort
}
}
manifest.Version = strings.TrimPrefix(version, "v")
}

// If no release notes specified, generate one from the latest tag, if present.
if manifest.ReleaseNotesURL == "" && BuildTagLatest != "" {
manifest.ReleaseNotesURL = manifest.HomepageURL + "releases/tag/" + BuildTagLatest
}

return &manifest, nil
}

Expand All @@ -80,3 +149,63 @@ func dumpPluginID(manifest *model.Manifest) {
func dumpPluginVersion(manifest *model.Manifest) {
fmt.Printf("%s", manifest.Version)
}

// applyManifest propagates the plugin_id into the server and webapp folders, as necessary
func applyManifest(manifest *model.Manifest) error {
if manifest.HasServer() {
// generate JSON representation of Manifest.
manifestBytes, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return err
}
manifestStr := string(manifestBytes)

// write generated code to file by using Go file template.
if err := os.WriteFile(
"server/manifest.go",
[]byte(fmt.Sprintf(pluginIDGoFileTemplate, manifestStr)),
0600,
); err != nil {
return errors.Wrap(err, "failed to write server/manifest.go")
}
}

if manifest.HasWebapp() {
// generate JSON representation of Manifest.
// JSON is very similar and compatible with JS's object literals. so, what we do here
// is actually JS code generation.
manifestBytes, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return err
}
manifestStr := string(manifestBytes)

// Escape newlines
manifestStr = strings.ReplaceAll(manifestStr, `\n`, `\\n`)

// write generated code to file by using JS file template.
if err := os.WriteFile(
"webapp/src/manifest.ts",
[]byte(fmt.Sprintf(pluginIDJSFileTemplate, manifestStr)),
0600,
); err != nil {
return errors.Wrap(err, "failed to open webapp/src/manifest.ts")
}
}

return nil
}

// distManifest writes the manifest file to the dist directory
func distManifest(manifest *model.Manifest) error {
manifestBytes, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return err
}

if err := os.WriteFile(fmt.Sprintf("dist/%s/plugin.json", manifest.Id), manifestBytes, 0600); err != nil {
return errors.Wrap(err, "failed to write plugin.json")
}

return nil
}
8 changes: 6 additions & 2 deletions build/setup.mk
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ ifeq ($(GO),)
$(error "go is not available: see https://golang.org/doc/install")
endif

# Ensure that the build tools are compiled. Go's caching makes this quick.
$(shell cd build/manifest && $(GO) build -o ../bin/manifest)
# Gather build variables to inject into the manifest tool
BUILD_HASH_SHORT = $(shell git rev-parse --short HEAD)
BUILD_TAG_LATEST = $(shell git describe --tags --match 'v*' --abbrev=0 2>/dev/null)
BUILD_TAG_CURRENT = $(shell git tag --points-at HEAD)

$(shell cd build/manifest && $(GO) build -ldflags '-X "main.BuildHashShort=$(BUILD_HASH_SHORT)" -X "main.BuildTagLatest=$(BUILD_TAG_LATEST)" -X "main.BuildTagCurrent=$(BUILD_TAG_CURRENT)"' -o ../bin/manifest)

# Ensure that the deployment tools are compiled. Go's caching makes this quick.
$(shell cd build/pluginctl && $(GO) build -o ../bin/pluginctl)
Expand Down
2 changes: 0 additions & 2 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
"description": "Google Calendar Integration",
"homepage_url": "https://github.com/mattermost/mattermost-plugin-google-calendar",
"support_url": "https://github.com/mattermost/mattermost-plugin-google-calendar/issues",
"release_notes_url": "https://github.com/mattermost/mattermost-plugin-google-calendar/releases/tag/v1.1.1",
"icon_path": "assets/profile-gcal.svg",
"version": "1.1.1",
"min_server_version": "6.3.0",
"server": {
"executables": {
Expand Down
2 changes: 1 addition & 1 deletion server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func main() {
plugin.NewWithEnv(
engine.Env{
Config: &config.Config{
PluginID: manifest.ID,
PluginID: manifest.Id,
PluginVersion: manifest.Version,
BuildHash: BuildHash,
BuildHashShort: BuildHashShort,
Expand Down
11 changes: 0 additions & 11 deletions server/manifest.go

This file was deleted.

4 changes: 0 additions & 4 deletions webapp/src/manifest.js

This file was deleted.

9 changes: 5 additions & 4 deletions webapp/src/manifest.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {id, version} from './manifest';
import manifest from './manifest';

test('Plugin id and version are defined', () => {
expect(id).toBeDefined();
expect(version).toBeDefined();
test('Plugin manifest, id and version are defined', () => {
expect(manifest).toBeDefined();
expect(manifest.id).toBeDefined();
expect(manifest.version).toBeDefined();
});
4 changes: 2 additions & 2 deletions webapp/src/plugin_id.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import {id} from './manifest.js';
import manifest from './manifest';

export const PluginId: string = id;
export const PluginId: string = manifest.id;

0 comments on commit a019ed7

Please sign in to comment.