diff --git a/docs/BUILD b/docs/BUILD
index ea493fbab..d941af922 100644
--- a/docs/BUILD
+++ b/docs/BUILD
@@ -58,8 +58,8 @@ genrule(
# Plugin versions to pull the docs from
plugins = {
"python": "v1.7.0",
- "java": "v0.4.0",
- "go": "v1.20.1",
+ "java": "v0.4.1",
+ "go": "v1.21.1",
"cc": "v0.4.0",
"shell": "v0.2.0",
"go-proto": "v0.3.0",
diff --git a/docs/lexicon.html b/docs/lexicon.html
index 351d56517..a0d33864c 100644
--- a/docs/lexicon.html
+++ b/docs/lexicon.html
@@ -951,6 +951,9 @@
Adds a new licence to a target. The assumption (as usual) is that if
multiple are added, they are options, so any one can be accepted.
+
+ Deprecated in favour of set_licence
.
+
@@ -980,6 +983,45 @@
+
+
+ set_licence
+
+
+ set_licence(target, licence)
+
+
+ Sets the target's licence to the given SPDX expression. Note that no normalisation is done on it.
+
+
+
+
+
+
+ Argument |
+ Default |
+ Type |
+ |
+
+
+
+
+ target |
+ |
+ str |
+ Label of the target to add the licence to. |
+
+
+ licence |
+ |
+ str |
+ SPDX expression defining licensing of the target |
+
+
+
+
+
+
get_licences
@@ -990,6 +1032,9 @@
Returns all the licences that are currently known to apply to a target.
+
+ Deprecated in favour of get_licence.
+
@@ -1013,6 +1058,39 @@
+
+
+ get_licence
+
+
+ get_licence(target)
+
+
+ Returns the licence expression of the given target.
+
+
+
+
+
+
+ Argument |
+ Default |
+ Type |
+ |
+
+
+
+
+ target |
+ |
+ str |
+ Label of the target to get the licence expression for. |
+
+
+
+
+
+
package
diff --git a/go.mod b/go.mod
index 50c5c30f7..7f5f20583 100644
--- a/go.mod
+++ b/go.mod
@@ -66,6 +66,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/github/go-spdx/v2 v2.3.1 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
@@ -85,6 +86,7 @@ require (
github.com/mostynb/zstdpool-syncpool v0.0.13 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pborman/uuid v1.2.1 // indirect
+ github.com/peterebden/go-spdx/v2 v2.4.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
diff --git a/go.sum b/go.sum
index 4c9adffbf..adca89360 100644
--- a/go.sum
+++ b/go.sum
@@ -61,6 +61,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/github/go-spdx/v2 v2.3.1 h1:ffGuHTbHuHzWPt53n8f9o8clGutuLPObo3zB4JAjxU8=
+github.com/github/go-spdx/v2 v2.3.1/go.mod h1:2ZxKsOhvBp+OYBDlsGnUMcchLeo2mrpEBn2L1C+U3IQ=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -187,6 +189,12 @@ github.com/peterebden/go-cli-init/v5 v5.2.1 h1:o+7EjS/PiYDvFUQRQVXJRjinmUzDjqcea
github.com/peterebden/go-cli-init/v5 v5.2.1/go.mod h1:0eBDoCJjj3BWyEtidFcP0TlD14cRtOtLCrTG/OVPB74=
github.com/peterebden/go-deferred-regex v1.1.0 h1:XNpUuRDU7iU59Toy+OJp9LUvsun5i3kEbe3c73oyCZg=
github.com/peterebden/go-deferred-regex v1.1.0/go.mod h1:EhIu4zsN+60671cx29rzxPSIEnDd2vOia4RFSOaYpRI=
+github.com/peterebden/go-spdx/v2 v2.4.1 h1:ah4SjMgKyFXqR5qkBGwKbtevTV3UK3+17OiPNw18GVM=
+github.com/peterebden/go-spdx/v2 v2.4.1/go.mod h1:6KrhEd9kpULcLYV6rGkV47Rolvt2IFcaRCyewNnLkLA=
+github.com/peterebden/go-spdx/v2 v2.4.2 h1:u+lPJXPPK1cJpCtDQBlUgu/cXgOg/dYUWKHUyHxakYI=
+github.com/peterebden/go-spdx/v2 v2.4.2/go.mod h1:6KrhEd9kpULcLYV6rGkV47Rolvt2IFcaRCyewNnLkLA=
+github.com/peterebden/go-spdx/v2 v2.4.3 h1:iiOEYy8+qvyTA3IBqVgLGpwTl5MAmU0Ej39w+8xr5gQ=
+github.com/peterebden/go-spdx/v2 v2.4.3/go.mod h1:6KrhEd9kpULcLYV6rGkV47Rolvt2IFcaRCyewNnLkLA=
github.com/peterebden/go-sri v1.1.1 h1:KK8yZ5/NX8YzWUY9QvhrP220QsvEKANLLAgvw35AkyU=
github.com/peterebden/go-sri v1.1.1/go.mod h1:KIRxtog35NfDWec5LV/iBqqfOEPcMpePZLc7EPE6goQ=
github.com/peterebden/tools v0.0.0-20190805132753-b2a0db951d2a h1:R4xz7BkSIQOS5CFmaadk2gwwOzy/u2Jvnimf1NHD2LY=
diff --git a/rules/builtins.build_defs b/rules/builtins.build_defs
index cffabf11e..a79a228d2 100644
--- a/rules/builtins.build_defs
+++ b/rules/builtins.build_defs
@@ -266,8 +266,12 @@ def add_entry_point(target:str, name:str, out:str):
def get_entry_points(target:str) -> dict:
pass
def add_licence(target:str, licence:str):
+ """Deprecated in favour of set_licence"""
+def set_licence(target:str, licence: str):
pass
def get_licences(target:str):
+ """Deprecated in favour of get_licence"""
+def get_licence(target:str):
pass
def get_command(target:str, config:str=''):
pass
diff --git a/src/build/build_step_test.go b/src/build/build_step_test.go
index e414e89f7..77ed2b399 100644
--- a/src/build/build_step_test.go
+++ b/src/build/build_step_test.go
@@ -290,22 +290,18 @@ func TestLicenceEnforcement(t *testing.T) {
// A license (non case sensitive) that is not in the list of accepted licenses will panic.
assert.Panics(t, func() {
- target.Licences = append(target.Licences, "Bsd")
+ target.Licence = "Bsd"
checkLicences(state, target)
}, "A target with a non-accepted licence will panic")
- // Accepting bsd should resolve the panic
- state.Config.Licences.Accept = append(state.Config.Licences.Accept, "BSD")
- checkLicences(state, target)
-
// Now construct a new "bad" target.
state, target = newState("//pkg:bad")
state.Config.Licences.Reject = append(state.Config.Licences.Reject, "gpl")
state.Config.Licences.Accept = append(state.Config.Licences.Accept, "mit")
// Adding an explicitly rejected licence should panic no matter what.
- target.Licences = append(target.Licences, "GPL")
assert.Panics(t, func() {
+ target.Licence = "GPL"
checkLicences(state, target)
}, "Trying to add GPL should panic (case insensitive)")
}
diff --git a/src/build/incrementality.go b/src/build/incrementality.go
index d9dc38dba..a88ac2c08 100644
--- a/src/build/incrementality.go
+++ b/src/build/incrementality.go
@@ -172,10 +172,7 @@ func ruleHash(state *core.BuildState, target *core.BuildTarget, runtime bool) []
h.Write([]byte(out))
}
}
- for _, licence := range target.Licences {
- h.Write([]byte(licence))
- }
-
+ h.Write([]byte(target.Licence))
for _, output := range target.OptionalOutputs {
h.Write([]byte(output))
}
diff --git a/src/build/incrementality_test.go b/src/build/incrementality_test.go
index dfb35d8ac..b9553b82e 100644
--- a/src/build/incrementality_test.go
+++ b/src/build/incrementality_test.go
@@ -41,7 +41,7 @@ var KnownFields = map[string]bool{
"PostBuildHash": true,
"outputs": true,
"namedOutputs": true,
- "Licences": true,
+ "Licence": true,
"Sandbox": true,
"Tools": true,
"namedTools": true,
diff --git a/src/core/BUILD b/src/core/BUILD
index 40a3bce6b..2e2487779 100644
--- a/src/core/BUILD
+++ b/src/core/BUILD
@@ -13,6 +13,7 @@ go_library(
"///third_party/go/github.com_coreos_go-semver//semver",
"///third_party/go/github.com_google_shlex//:shlex",
"///third_party/go/github.com_peterebden_go-deferred-regex//:go-deferred-regex",
+ "///third_party/go/github.com_peterebden_go-spdx_v2//spdxexp",
"///third_party/go/github.com_pkg_xattr//:xattr",
"///third_party/go/github.com_please-build_gcfg//:gcfg",
"///third_party/go/github.com_please-build_gcfg//types",
diff --git a/src/core/build_target.go b/src/core/build_target.go
index 2b9ea38d3..81f0a221e 100644
--- a/src/core/build_target.go
+++ b/src/core/build_target.go
@@ -11,6 +11,7 @@ import (
"sync/atomic"
"time"
+ "github.com/peterebden/go-spdx/v2/spdxexp"
"golang.org/x/sync/errgroup"
"github.com/thought-machine/please/src/fs"
@@ -152,8 +153,8 @@ type BuildTarget struct {
// Acceptable hashes of the outputs of this rule. If the output doesn't match any of these
// it's an error at build time. Can be used to validate third-party deps.
Hashes []string
- // Licences that this target is subject to.
- Licences []string
+ // SPDX licence expression that this target is subject to.
+ Licence string
// Any secrets that this rule requires.
// Secrets are similar to sources but are always absolute system paths and affect the hash
// differently; they are not used to determine the hash for retrieving a file from cache, but
@@ -1764,17 +1765,6 @@ func (target *BuildTarget) insert(sl []string, s string) []string {
return append(sl, s)
}
-// AddLicence adds a licence to the target if it's not already there.
-func (target *BuildTarget) AddLicence(licence string) {
- licence = strings.TrimSpace(licence)
- for _, l := range target.Licences {
- if l == licence {
- return
- }
- }
- target.Licences = append(target.Licences, licence)
-}
-
// AddHash adds a new acceptable hash to the target.
func (target *BuildTarget) AddHash(hash string) {
target.Hashes = append(target.Hashes, hash)
@@ -1898,27 +1888,18 @@ func (target *BuildTarget) PackageDir() string {
}
// CheckLicences checks the target's licences against the accepted/rejected list.
-// It returns the licence that was accepted and an error if it did not match.
+// It returns the licence expression that was accepted and an error if it did not match.
func (target *BuildTarget) CheckLicences(config *Configuration) (string, error) {
- if len(target.Licences) == 0 {
+ if target.Licence == "" || (len(config.Licences.Accept) == 0 && len(config.Licences.Reject) == 0) {
return "", nil
}
- for _, licence := range target.Licences {
- for _, reject := range config.Licences.Reject {
- if strings.EqualFold(reject, licence) {
- return "", fmt.Errorf("Target %s is licensed %s, which is explicitly rejected for this repository", target.Label, licence)
- }
- }
- for _, accept := range config.Licences.Accept {
- if strings.EqualFold(accept, licence) {
- return licence, nil // Note licences are assumed to be an 'or', ie. any one of them can be accepted.
- }
- }
- }
- if len(config.Licences.Accept) > 0 {
- return "", fmt.Errorf("None of the licences for %s are accepted in this repository: %s", target.Label, strings.Join(target.Licences, ", "))
+ accepted, err := spdxexp.ExpressionSatisfies(target.Licence, config.AcceptedLicences())
+ if err != nil {
+ return "", fmt.Errorf("Target %s has invalid licence '%s': %s", target, target.Licence, err)
+ } else if accepted == "" {
+ return "", fmt.Errorf("The licences for %s are not accepted in this repository: %s", target, target.Licence)
}
- return "", nil
+ return accepted, nil
}
// BuildTargets makes a slice of build targets sortable by their labels.
diff --git a/src/core/build_target_test.go b/src/core/build_target_test.go
index d9a6cea38..97a672eb2 100644
--- a/src/core/build_target_test.go
+++ b/src/core/build_target_test.go
@@ -954,19 +954,24 @@ func TestIsTool(t *testing.T) {
func TestCheckLicences(t *testing.T) {
config := DefaultConfiguration()
- config.Licences.Accept = []string{"BSD"}
+ config.Licences.Accept = []string{"BSD", "Apache-2.0"}
config.Licences.Reject = []string{"GPL"}
target := makeTarget1("//src/core/test_data/project", "PUBLIC")
- target.Licences = []string{"BSD", "GPL"}
+ target.Licence = "BSD OR GPL"
accepted, err := target.CheckLicences(config)
assert.NoError(t, err)
assert.Equal(t, "BSD", accepted)
- target.Licences = []string{"MIT", "GPL"}
+ target.Licence = "MIT OR GPL"
accepted, err = target.CheckLicences(config)
assert.Error(t, err)
assert.Equal(t, "", accepted)
+
+ target.Licence = "BSD AND Apache-2.0 OR GPL"
+ accepted, err = target.CheckLicences(config)
+ assert.NoError(t, err)
+ assert.Equal(t, "BSD AND Apache-2.0", accepted)
}
func makeTarget1(label, visibility string, deps ...*BuildTarget) *BuildTarget {
diff --git a/src/core/config.go b/src/core/config.go
index 526f70388..5e6ad380c 100644
--- a/src/core/config.go
+++ b/src/core/config.go
@@ -11,6 +11,7 @@ import (
"path/filepath"
"reflect"
"runtime"
+ "slices"
"sort"
"strconv"
"strings"
@@ -19,6 +20,7 @@ import (
"github.com/coreos/go-semver/semver"
"github.com/google/shlex"
+ "github.com/peterebden/go-spdx/v2/spdxexp"
"github.com/please-build/gcfg"
gcfgtypes "github.com/please-build/gcfg/types"
"github.com/thought-machine/go-flags"
@@ -368,7 +370,10 @@ func defaultPathIfExists(conf *string, dir, file string) {
// DefaultConfiguration returns the default configuration object with no overrides.
// N.B. Slice fields are not populated by this (since it interferes with reading them)
func DefaultConfiguration() *Configuration {
- config := Configuration{buildEnvStored: &storedBuildEnv{}}
+ config := Configuration{
+ buildEnvStored: &storedBuildEnv{},
+ acceptedLicences: &storedAcceptedLicences{},
+ }
config.Please.SelfUpdate = true
config.Please.Autoclean = true
config.Please.DownloadLocation = "https://get.please.build"
@@ -684,17 +689,20 @@ type Configuration struct {
Bazel struct {
Compatibility bool `help:"Activates limited Bazel compatibility mode. When this is active several rule arguments are available under different names (e.g. compiler_flags -> copts etc), the WORKSPACE file is interpreted, Makefile-style replacements like $< and $@ are made in genrule commands, etc.\nNote that Skylark is not generally supported and many aspects of compatibility are fairly superficial; it's unlikely this will work for complex setups of either tool." var:"BAZEL_COMPATIBILITY"`
} `help:"Bazel is an open-sourced version of Google's internal build tool. Please draws a lot of inspiration from the original tool although the two have now diverged in various ways.\nNonetheless, if you've used Bazel, you will likely find Please familiar."`
-
- // buildEnvStored is a cached form of BuildEnv.
- buildEnvStored *storedBuildEnv
-
FeatureFlags struct {
+ SPDXLicencesOnly bool `help:"Licences on build targets can only be strings containing SPDX expressions, not lists."`
} `help:"Flags controlling preview features for the next release. Typically these config options gate breaking changes and only have a lifetime of one major release."`
Metrics struct {
PrometheusGatewayURL string `help:"The gateway URL to push prometheus updates to."`
Timeout cli.Duration `help:"timeout for pushing to the gateway. Defaults to 2 seconds." `
PushHostInfo bool `help:"Whether to push host info"`
} `help:"Settings for collecting metrics."`
+
+ // buildEnvStored is a cached form of BuildEnv.
+ buildEnvStored *storedBuildEnv
+
+ // acceptedLicences is a slightly processed form of Licences.Accept
+ acceptedLicences *storedAcceptedLicences
}
// An Alias represents aliases in the config.
@@ -742,6 +750,11 @@ type storedBuildEnv struct {
Once sync.Once
}
+type storedAcceptedLicences struct {
+ Licences []string
+ Once sync.Once
+}
+
// Hash returns a hash of the parts of this configuration that affect building targets in general.
// Most parts are considered not to (e.g. cache settings) or affect specific targets (e.g. changing
// tool paths which get accounted for on the targets that use them).
@@ -844,6 +857,25 @@ func (config *Configuration) getBuildEnv(includePath bool, includeUnsafe bool) B
return env
}
+// AcceptedLicences returns the set of licences accepted in this repository
+func (config *Configuration) AcceptedLicences() []string {
+ config.acceptedLicences.Once.Do(func() {
+ l := make([]string, len(config.Licences.Accept))
+ for i, licence := range config.Licences.Accept {
+ l[i] = strings.ReplaceAll(strings.ReplaceAll(licence, " ", "-"), ",", "-")
+ }
+ if ok, invalid := spdxexp.ValidateLicenses(l); !ok {
+ log.Warning("The following accepted licences are not valid licence names: %s", strings.Join(invalid, ", "))
+ // We now have to remove these, otherwise the SPDX parser gets upset later on
+ for _, licence := range invalid {
+ l = slices.DeleteFunc(l, func(l string) bool { return l == licence })
+ }
+ }
+ config.acceptedLicences.Licences = l
+ })
+ return config.acceptedLicences.Licences
+}
+
// TagsToFields returns a map of string represent the properties of CONFIG object to the config Structfield
func (config *Configuration) TagsToFields() map[string]reflect.StructField {
tags := make(map[string]reflect.StructField)
diff --git a/src/core/stamp.go b/src/core/stamp.go
index e1e71e4b5..dd2ae667f 100644
--- a/src/core/stamp.go
+++ b/src/core/stamp.go
@@ -22,11 +22,15 @@ func StampFile(config *Configuration, target *BuildTarget) []byte {
func populateStampInfo(config *Configuration, target *BuildTarget, info *stampInfo) {
accepted, _ := target.CheckLicences(config)
- info.Targets[target.Label] = targetInfo{
- Licences: target.Licences,
+ ti := targetInfo{
+ Licence: target.Licence,
AcceptedLicence: accepted,
Labels: target.Labels,
}
+ if target.Licence != "" {
+ ti.Licences = []string{target.Licence}
+ }
+ info.Targets[target.Label] = ti
for _, dep := range target.Dependencies() {
if _, present := info.Targets[dep.Label]; !present {
populateStampInfo(config, dep, info)
@@ -40,6 +44,7 @@ type stampInfo struct {
type targetInfo struct {
Labels []string `json:"labels,omitempty"`
- Licences []string `json:"licences,omitempty"`
+ Licence string `json:"licence,omitempty"`
+ Licences []string `json:"licences,omitempty"` // Deprecated in favour of Licence
AcceptedLicence string `json:"accepted_licence,omitempty"`
}
diff --git a/src/core/stamp_test.go b/src/core/stamp_test.go
index 1747b9230..d3e8d71e7 100644
--- a/src/core/stamp_test.go
+++ b/src/core/stamp_test.go
@@ -14,14 +14,14 @@ func TestStampFile(t *testing.T) {
t3 := NewBuildTarget(ParseBuildLabel("//third_party/go:errors", ""))
t1.AddLabel("go")
t3.AddLabel("go_get:github.com/pkg/errors")
- t3.AddLicence("bsd-2-clause")
+ t3.Licence = "bsd-2-clause"
t1.AddDependency(t2.Label)
t1.resolveDependency(t2.Label, t2)
t1.AddDependency(t3.Label)
t1.resolveDependency(t3.Label, t3)
t2.AddDependency(t3.Label)
t2.resolveDependency(t3.Label, t3)
- expected := []byte(`{
+ const expected = `{
"targets": {
"//src/core:core": {
"labels": [
@@ -33,12 +33,14 @@ func TestStampFile(t *testing.T) {
"labels": [
"go_get:github.com/pkg/errors"
],
+ "licence": "bsd-2-clause",
"licences": [
"bsd-2-clause"
],
- "accepted_licence": "bsd-2-clause"
+ "accepted_licence": "BSD-2-Clause"
}
}
-}`)
- assert.Equal(t, expected, StampFile(config, t1))
+}`
+
+ assert.Equal(t, expected, string(StampFile(config, t1)))
}
diff --git a/src/parse/asp/BUILD b/src/parse/asp/BUILD
index 486fa0fef..239273939 100644
--- a/src/parse/asp/BUILD
+++ b/src/parse/asp/BUILD
@@ -12,6 +12,7 @@ go_library(
"///third_party/go/github.com_Masterminds_semver_v3//:v3",
"///third_party/go/github.com_manifoldco_promptui//:promptui",
"///third_party/go/github.com_please-build_gcfg//types",
+ "///third_party/go/github.com_peterebden_go-deferred-regex//:go-deferred-regex",
"//src/cli",
"//src/cli/logging",
"//src/cmap",
diff --git a/src/parse/asp/builtins.go b/src/parse/asp/builtins.go
index 276957a39..a9cd2d480 100644
--- a/src/parse/asp/builtins.go
+++ b/src/parse/asp/builtins.go
@@ -15,6 +15,7 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/manifoldco/promptui"
+ "github.com/peterebden/go-deferred-regex"
"github.com/thought-machine/please/src/cli"
"github.com/thought-machine/please/src/core"
@@ -24,6 +25,8 @@ import (
// A nativeFunc is a function that implements a builtin function natively.
type nativeFunc func(*scope, []pyObject) pyObject
+var normaliseLicence = deferredregex.DeferredRegex{Re: `[ (),]`}
+
// registerBuiltins sets up the "special" builtins that map to native code.
func registerBuiltins(s *scope) {
const varargs = true
@@ -70,7 +73,9 @@ func registerBuiltins(s *scope) {
setNativeCode(s, "add_entry_point", addEntryPoint)
setNativeCode(s, "get_entry_points", getEntryPoints)
setNativeCode(s, "add_licence", addLicence)
+ setNativeCode(s, "set_licence", setLicence)
setNativeCode(s, "get_licences", getLicences)
+ setNativeCode(s, "get_licence", getLicence)
setNativeCode(s, "get_command", getCommand)
setNativeCode(s, "set_command", setCommand)
setNativeCode(s, "json", valueAsJSON)
@@ -1314,14 +1319,31 @@ func getEntryPoints(s *scope, args []pyObject) pyObject {
// addLicence adds a licence to a target.
func addLicence(s *scope, args []pyObject) pyObject {
+ s.Assert(!s.state.Config.FeatureFlags.SPDXLicencesOnly, "The add_licence builtin has been replaced with set_licence")
+ target := getTargetPost(s, string(args[0].(pyString)))
+ if target.Licence != "" {
+ target.Licence += " OR "
+ }
+ target.Licence += normaliseLicence.ReplaceAllString(strings.TrimSpace(string(args[1].(pyString))), "-")
+ return None
+}
+
+// setLicence sets the target's licence to the given string. Note that it must be a valid SPDX identifier.
+func setLicence(s *scope, args []pyObject) pyObject {
target := getTargetPost(s, string(args[0].(pyString)))
- target.AddLicence(string(args[1].(pyString)))
+ target.Licence += string(args[1].(pyString))
return None
}
// getLicences returns the licences for a single target.
func getLicences(s *scope, args []pyObject) pyObject {
- return fromStringList(getTargetPost(s, string(args[0].(pyString))).Licences)
+ s.Assert(!s.state.Config.FeatureFlags.SPDXLicencesOnly, "The get_licences builtin has been replaced with get_licence")
+ return pyList{pyString(getTargetPost(s, string(args[0].(pyString))).Licence)}
+}
+
+// getLicence returns the licence for a single target.
+func getLicence(s *scope, args []pyObject) pyObject {
+ return pyString(getTargetPost(s, string(args[0].(pyString))).Licence)
}
// getCommand gets the command of a target, optionally for a configuration.
diff --git a/src/parse/asp/targets.go b/src/parse/asp/targets.go
index d916a1a13..58fa39c2d 100644
--- a/src/parse/asp/targets.go
+++ b/src/parse/asp/targets.go
@@ -273,8 +273,33 @@ func populateTarget(s *scope, t *core.BuildTarget, args []pyObject) {
addDependencies(s, "internal_deps", args[internalDepsBuildRuleArgIdx], t, false, true)
addStrings(s, "labels", args[labelsBuildRuleArgIdx], t.AddLabel)
addStrings(s, "hashes", args[hashesBuildRuleArgIdx], t.AddHash)
- addStrings(s, "licences", args[licencesBuildRuleArgIdx], t.AddLicence)
addStrings(s, "requires", args[requiresBuildRuleArgIdx], t.AddRequire)
+ if arg := args[licencesBuildRuleArgIdx]; arg != None {
+ if expr, ok := arg.(pyString); ok {
+ // TODO(v18): Remove the replace here once all licences are expected to be SPDX expressions
+ t.Licence = strings.ReplaceAll(string(expr), " ", "-")
+ } else {
+ // TODO(v18): Remove all this once strings are the only option.
+ s.Assert(!s.state.Config.FeatureFlags.SPDXLicencesOnly, "The licences argument must be a string")
+ l, ok := asList(arg)
+ s.Assert(ok, "The licences argument must be a string or list (was %s)", arg.Type())
+ if len(l) == 1 {
+ // minor optimisation: avoid allocating a slice etc for the overwhelmingly common case of a single licence
+ str, ok := l[0].(pyString)
+ s.Assert(ok, "Items in the licences argument must be strings (was %s)", str.Type())
+ t.Licence = strings.ReplaceAll(string(str), " ", "-")
+ } else {
+ l2 := make([]string, len(l))
+ for i, x := range l {
+ str, ok := x.(pyString)
+ s.Assert(ok, "Items in the licences argument must be strings (was %s)", str.Type())
+ l2[i] = strings.ReplaceAll(string(str), " ", "-")
+ }
+ // Passing a list to Please is implicitly a series of alternative licences
+ t.Licence = strings.Join(l2, " OR ")
+ }
+ }
+ }
if vis, ok := asList(args[visibilityBuildRuleArgIdx]); ok && len(vis) != 0 {
if v, ok := vis[0].(pyString); ok && v == "PUBLIC" {
t.Visibility = core.WholeGraph
diff --git a/third_party/go/BUILD b/third_party/go/BUILD
index ce8633671..9e63b34db 100644
--- a/third_party/go/BUILD
+++ b/third_party/go/BUILD
@@ -686,3 +686,15 @@ go_repo(
module = "github.com/grpc-ecosystem/go-grpc-prometheus",
version = "v1.2.0",
)
+
+go_repo(
+ licences = ["MIT"],
+ module = "github.com/peterebden/go-spdx/v2",
+ version = "v2.4.3",
+)
+
+go_repo(
+ licences = ["MIT"],
+ module = "github.com/github/go-spdx/v2",
+ version = "v2.3.1",
+)