Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0a9054e5 authored by Paul Duffin's avatar Paul Duffin Committed by Gerrit Code Review
Browse files

Merge "Support handling build releases in sdk snapshot"

parents f7db6eba 1812294f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ bootstrap_go_package {
    ],
    srcs: [
        "bp.go",
        "build_release.go",
        "exports.go",
        "member_trait.go",
        "member_type.go",
@@ -25,6 +26,7 @@ bootstrap_go_package {
    testSrcs: [
        "bootclasspath_fragment_sdk_test.go",
        "bp_test.go",
        "build_release_test.go",
        "cc_sdk_test.go",
        "compat_config_sdk_test.go",
        "exports_test.go",

sdk/build_release.go

0 → 100644
+160 −0
Original line number Diff line number Diff line
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sdk

import (
	"fmt"
	"strings"
)

// Supports customizing sdk snapshot output based on target build release.

// buildRelease represents the version of a build system used to create a specific release.
//
// The name of the release, is the same as the code for the dessert release, e.g. S, T, etc.
type buildRelease struct {
	// The name of the release, e.g. S, T, etc.
	name string

	// The index of this structure within the buildReleases list.
	ordinal int
}

// String returns the name of the build release.
func (s *buildRelease) String() string {
	return s.name
}

// buildReleaseSet represents a set of buildRelease objects.
type buildReleaseSet struct {
	// Set of *buildRelease represented as a map from *buildRelease to struct{}.
	contents map[*buildRelease]struct{}
}

// addItem adds a build release to the set.
func (s *buildReleaseSet) addItem(release *buildRelease) {
	s.contents[release] = struct{}{}
}

// addRange adds all the build releases from start (inclusive) to end (inclusive).
func (s *buildReleaseSet) addRange(start *buildRelease, end *buildRelease) {
	for i := start.ordinal; i <= end.ordinal; i += 1 {
		s.addItem(buildReleases[i])
	}
}

// contains returns true if the set contains the specified build release.
func (s *buildReleaseSet) contains(release *buildRelease) bool {
	_, ok := s.contents[release]
	return ok
}

// String returns a string representation of the set, sorted from earliest to latest release.
func (s *buildReleaseSet) String() string {
	list := []string{}
	for _, release := range buildReleases {
		if _, ok := s.contents[release]; ok {
			list = append(list, release.name)
		}
	}
	return fmt.Sprintf("[%s]", strings.Join(list, ","))
}

var (
	// nameToBuildRelease contains a map from name to build release.
	nameToBuildRelease = map[string]*buildRelease{}

	// buildReleases lists all the available build releases.
	buildReleases = []*buildRelease{}

	// allBuildReleaseSet is the set of all build releases.
	allBuildReleaseSet = &buildReleaseSet{contents: map[*buildRelease]struct{}{}}

	// Add the build releases from oldest to newest.
	buildReleaseS = initBuildRelease("S")
	buildReleaseT = initBuildRelease("T")
)

// initBuildRelease creates a new build release with the specified name.
func initBuildRelease(name string) *buildRelease {
	ordinal := len(nameToBuildRelease)
	release := &buildRelease{name: name, ordinal: ordinal}
	nameToBuildRelease[name] = release
	buildReleases = append(buildReleases, release)
	allBuildReleaseSet.addItem(release)
	return release
}

// latestBuildRelease returns the latest build release, i.e. the last one added.
func latestBuildRelease() *buildRelease {
	return buildReleases[len(buildReleases)-1]
}

// nameToRelease maps from build release name to the corresponding build release (if it exists) or
// the error if it does not.
func nameToRelease(name string) (*buildRelease, error) {
	if r, ok := nameToBuildRelease[name]; ok {
		return r, nil
	}

	return nil, fmt.Errorf("unknown release %q, expected one of %s", name, allBuildReleaseSet)
}

// parseBuildReleaseSet parses a build release set string specification into a build release set.
//
// The specification consists of one of the following:
// * a single build release name, e.g. S, T, etc.
// * a closed range (inclusive to inclusive), e.g. S-T
// * an open range, e.g. T+.
//
// This returns the set if the specification was valid or an error.
func parseBuildReleaseSet(specification string) (*buildReleaseSet, error) {
	set := &buildReleaseSet{contents: map[*buildRelease]struct{}{}}

	if strings.HasSuffix(specification, "+") {
		rangeStart := strings.TrimSuffix(specification, "+")
		start, err := nameToRelease(rangeStart)
		if err != nil {
			return nil, err
		}
		end := latestBuildRelease()
		set.addRange(start, end)
	} else if strings.Contains(specification, "-") {
		limits := strings.SplitN(specification, "-", 2)
		start, err := nameToRelease(limits[0])
		if err != nil {
			return nil, err
		}

		end, err := nameToRelease(limits[1])
		if err != nil {
			return nil, err
		}

		if start.ordinal > end.ordinal {
			return nil, fmt.Errorf("invalid closed range, start release %q is later than end release %q", start.name, end.name)
		}

		set.addRange(start, end)
	} else {
		release, err := nameToRelease(specification)
		if err != nil {
			return nil, err
		}
		set.addItem(release)
	}

	return set, nil
}
+100 −0
Original line number Diff line number Diff line
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sdk

import (
	"fmt"
	"testing"

	"android/soong/android"
)

// Tests for build_release.go

var (
	// Some additional test specific releases that are added after the currently supported ones and
	// so are treated as being for future releases.
	buildReleaseFuture1 = initBuildRelease("F1")
	buildReleaseFuture2 = initBuildRelease("F2")
)

func TestNameToRelease(t *testing.T) {
	t.Run("single release", func(t *testing.T) {
		release, err := nameToRelease("S")
		android.AssertDeepEquals(t, "errors", nil, err)
		android.AssertDeepEquals(t, "release", buildReleaseS, release)
	})
	t.Run("invalid release", func(t *testing.T) {
		release, err := nameToRelease("A")
		android.AssertDeepEquals(t, "release", (*buildRelease)(nil), release)
		// Uses a wildcard in the error message to allow for additional build releases to be added to
		// the supported set without breaking this test.
		android.FailIfNoMatchingErrors(t, `unknown release "A", expected one of \[S,T.*,F1,F2\]`, []error{err})
	})
}

func TestParseBuildReleaseSet(t *testing.T) {
	t.Run("single release", func(t *testing.T) {
		set, err := parseBuildReleaseSet("S")
		android.AssertDeepEquals(t, "errors", nil, err)
		android.AssertStringEquals(t, "set", "[S]", set.String())
	})
	t.Run("open range", func(t *testing.T) {
		set, err := parseBuildReleaseSet("F1+")
		android.AssertDeepEquals(t, "errors", nil, err)
		android.AssertStringEquals(t, "set", "[F1,F2]", set.String())
	})
	t.Run("closed range", func(t *testing.T) {
		set, err := parseBuildReleaseSet("S-F1")
		android.AssertDeepEquals(t, "errors", nil, err)
		android.AssertStringEquals(t, "set", "[S,T,F1]", set.String())
	})
	invalidAReleaseMessage := `unknown release "A", expected one of ` + allBuildReleaseSet.String()
	t.Run("invalid release", func(t *testing.T) {
		set, err := parseBuildReleaseSet("A")
		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
	})
	t.Run("invalid release in open range", func(t *testing.T) {
		set, err := parseBuildReleaseSet("A+")
		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
	})
	t.Run("invalid release in closed range start", func(t *testing.T) {
		set, err := parseBuildReleaseSet("A-S")
		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
	})
	t.Run("invalid release in closed range end", func(t *testing.T) {
		set, err := parseBuildReleaseSet("T-A")
		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
	})
	t.Run("invalid closed range reversed", func(t *testing.T) {
		set, err := parseBuildReleaseSet("F1-S")
		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), `invalid closed range, start release "F1" is later than end release "S"`)
	})
}

func TestBuildReleaseSetContains(t *testing.T) {
	t.Run("contains", func(t *testing.T) {
		set, _ := parseBuildReleaseSet("F1-F2")
		android.AssertBoolEquals(t, "set contains F1", true, set.contains(buildReleaseFuture1))
		android.AssertBoolEquals(t, "set does not contain S", false, set.contains(buildReleaseS))
		android.AssertBoolEquals(t, "set contains F2", true, set.contains(buildReleaseFuture2))
		android.AssertBoolEquals(t, "set does not contain T", false, set.contains(buildReleaseT))
	})
}