[cipd] add platforms attribute for packages
This patch adds platforms attribute for <package> tags in manifests.
Currently jiri does not generate locks for linux-arm64 packages
as not all packages are targeted for linux-arm64. The newly added
'platforms' allows user to specify the exact platforms the packages
are targeted, which make it possible for jiri to generate correct
locks for additional platforms including linux-arm64.
Change-Id: Icb0bc5c615c329f10f688f55a1c209e92105cbf1
diff --git a/cipd/cipd.go b/cipd/cipd.go
index cb2e839..af076f2 100644
--- a/cipd/cipd.go
+++ b/cipd/cipd.go
@@ -503,6 +503,15 @@
Arch string
}
+// NewPlatform parses a platform string into Platform struct.
+func NewPlatform(s string) (Platform, error) {
+ fields := strings.Split(s, "-")
+ if len(fields) != 2 {
+ return Platform{"", ""}, fmt.Errorf("illegal platform %q", s)
+ }
+ return Platform{fields[0], fields[1]}, nil
+}
+
// String generates a string represents the Platform in "OS"-"Arch" form.
func (p Platform) String() string {
return p.OS + "-" + p.Arch
@@ -587,7 +596,7 @@
func Expand(cipdPath string, platforms []Platform) ([]string, error) {
output := make([]string, 0)
//expanders := make([]Expander, 0)
- if !templateRE.MatchString(cipdPath) {
+ if !MustExpand(cipdPath) {
output = append(output, cipdPath)
return output, nil
}
@@ -605,6 +614,61 @@
return output, nil
}
+// Decl method expands a cipdPath that contains ${platform}, ${os}, ${arch}
+// with information in platforms. Unlike the Expand method which
+// returns a list of expanded cipd paths, the Decl method only returns a
+// single path containing all platforms. For example, if platforms contain
+// "linux-amd64" and "linux-arm64", ${platform} will be replaced to
+// ${platform=linux-amd64,linux-arm64}. This is a workaround for a limitation
+// in 'cipd ensure-file-resolve' which requires the header of '.ensure' file
+// to contain all available platforms. But in some cases, a package may miss
+// a particular platform, which will cause a crash on this cipd command. By
+// explicitly list all supporting platforms in the cipdPath, we can avoid
+// crashing cipd.
+func Decl(cipdPath string, platforms []Platform) (string, error) {
+ if !MustExpand(cipdPath) || len(platforms) == 0 {
+ return cipdPath, nil
+ }
+
+ osMap := make(map[string]bool)
+ platMap := make(map[string]bool)
+ archMap := make(map[string]bool)
+
+ replacedOS := "${os="
+ replacedArch := "${arch="
+ replacedPlat := "${platform="
+
+ for _, plat := range platforms {
+ if _, ok := osMap[plat.OS]; !ok {
+ replacedOS += plat.OS + ","
+ osMap[plat.OS] = true
+ }
+ if _, ok := archMap[plat.Arch]; !ok {
+ replacedArch += plat.Arch + ","
+ archMap[plat.Arch] = true
+ }
+ if _, ok := platMap[plat.String()]; !ok {
+ replacedPlat += plat.String() + ","
+ platMap[plat.String()] = true
+ }
+ }
+ replacedOS = replacedOS[:len(replacedOS)-1] + "}"
+ replacedArch = replacedArch[:len(replacedArch)-1] + "}"
+ replacedPlat = replacedPlat[:len(replacedPlat)-1] + "}"
+
+ cipdPath = strings.Replace(cipdPath, "${os}", replacedOS, -1)
+ cipdPath = strings.Replace(cipdPath, "${arch}", replacedArch, -1)
+ cipdPath = strings.Replace(cipdPath, "${platform}", replacedPlat, -1)
+ return cipdPath, nil
+}
+
+// MustExpand checks if template usages such as "${platform}" exist
+// in cipdPath. If they exist, this function will return true. Otherwise
+// it returns false.
+func MustExpand(cipdPath string) bool {
+ return templateRE.MatchString(cipdPath)
+}
+
// DefaultPlatforms returns a slice of Platform objects that are currently
// validated by jiri.
func DefaultPlatforms() []Platform {
diff --git a/cipd/cipd_test.go b/cipd/cipd_test.go
index e96cce6..25a8061 100644
--- a/cipd/cipd_test.go
+++ b/cipd/cipd_test.go
@@ -294,3 +294,43 @@
}
}
}
+
+func TestMustExpand(t *testing.T) {
+ tests := map[string]bool{
+ "fuchsia/clang/${platform}": true,
+ "fuchsia/clang/${os}-${arch}": true,
+ "fuchsia/clang/${os=linux}-${arch=amd64}": true,
+ "fuchsia/clang/linux-amd64": false,
+ }
+ for k, v := range tests {
+ if MustExpand(k) != v {
+ t.Errorf("MustExpand failed on package %q, expecting %v got %v", k, v, MustExpand(k))
+ }
+ }
+}
+
+func TestDecl(t *testing.T) {
+ platforms := []Platform{
+ Platform{"linux", "amd64"},
+ Platform{"linux", "arm64"},
+ Platform{"mac", "amd64"},
+ }
+
+ tests := map[string]string{
+ "fuchsia/clang/${platform}": "fuchsia/clang/${platform=linux-amd64,linux-arm64,mac-amd64}",
+ "fuchsia/clang/${os}-${arch}": "fuchsia/clang/${os=linux,mac}-${arch=amd64,arm64}",
+ "fuchsia/clang/${os=linux}-${arch}": "fuchsia/clang/${os=linux}-${arch=amd64,arm64}",
+ "fuchsia/clang/${os=linux}-${arch=amd64}": "fuchsia/clang/${os=linux}-${arch=amd64}",
+ "fuchsia/clang/linux-amd64": "fuchsia/clang/linux-amd64",
+ }
+
+ for k, v := range tests {
+ cipdPath, err := Decl(k, platforms)
+ if err != nil {
+ t.Errorf("Decl failed on cipdPath %q due to error: %v", k, err)
+ }
+ if cipdPath != v {
+ t.Errorf("test on %q failed: expecting %q, got %q", k, v, cipdPath)
+ }
+ }
+}
diff --git a/project/manifest.go b/project/manifest.go
index b1a9b2e..5191459 100644
--- a/project/manifest.go
+++ b/project/manifest.go
@@ -428,6 +428,7 @@
Version string `xml:"version,attr"`
Path string `xml:"path,attr,omitempty"`
Internal bool `xml:"internal,attr,omitempty"`
+ Platforms string `xml:"platforms,attr,omitempty"`
Instances []PackageInstance `xml:"instance"`
XMLName struct{} `xml:"package"`
}
@@ -436,8 +437,8 @@
type Packages map[PackageKey]Package
-func (pkg Package) Key() PackageKey {
- return PackageKey(pkg.Name)
+func (p Package) Key() PackageKey {
+ return PackageKey(p.Name)
}
type PackageInstance struct {
@@ -446,6 +447,41 @@
XMLName struct{} `xml:"instance"`
}
+// FillDefaults function fills default platforms information into
+// Package struct if it is not defined and path is using template.
+func (p *Package) FillDefaults() error {
+ if cipd.MustExpand(p.Name) && p.Platforms == "" {
+ for _, v := range cipd.DefaultPlatforms() {
+ p.Platforms += v.String() + ","
+ }
+ if p.Platforms[len(p.Platforms)-1] == ',' {
+ p.Platforms = p.Platforms[:len(p.Platforms)-1]
+ }
+ }
+ return nil
+}
+
+// GetPlatforms returns the platforms information of
+// this Package struct.
+func (p *Package) GetPlatforms() ([]cipd.Platform, error) {
+ if err := p.FillDefaults(); err != nil {
+ return nil, err
+ }
+ retList := make([]cipd.Platform, 0)
+ platStrs := strings.Split(p.Platforms, ",")
+ for _, platStr := range platStrs {
+ if platStr == "" {
+ continue
+ }
+ plat, err := cipd.NewPlatform(platStr)
+ if err != nil {
+ return nil, err
+ }
+ retList = append(retList, plat)
+ }
+ return retList, nil
+}
+
// LoadManifest loads the manifest, starting with the .jiri_manifest file,
// resolving remote and local imports. Returns the projects specified by
// the manifest.
@@ -637,7 +673,25 @@
// to avoid hardcoding platform names in Jiri
var ensureFileBuf bytes.Buffer
if !ignoreCryptoCheck {
+ // Collect platforms used by this project
+ allPlats := make(map[string]cipd.Platform)
+ // CIPD ensure-file-resolve requires $VerifiedPlatform to be present
+ // even if the package name is not using ${platform} template.
+ // Put DefaultPlatforms into header to walkaround this issue.
for _, plat := range cipd.DefaultPlatforms() {
+ allPlats[plat.String()] = plat
+ }
+ for _, pkg := range pkgs {
+ plats, err := pkg.GetPlatforms()
+ if err != nil {
+ return "", err
+ }
+ for _, plat := range plats {
+ allPlats[plat.String()] = plat
+ }
+ }
+
+ for _, plat := range allPlats {
ensureFileBuf.WriteString(fmt.Sprintf("$VerifiedPlatform %s\n", plat))
}
versionFileName := ensureFilePath[:len(ensureFilePath)-len(".ensure")] + ".version"
@@ -695,10 +749,10 @@
return ensureFilePath, nil
}
-func (pkg *Package) cipdDecl() (string, error) {
+func (p *Package) cipdDecl() (string, error) {
var buf bytes.Buffer
// Write "@Subdir" line to cipd declaration
- subdir := pkg.Path
+ subdir := p.Path
tmpl, err := template.New("pack").Parse(subdir)
if err != nil {
return "", fmt.Errorf("parsing package path %q failed", subdir)
@@ -708,7 +762,15 @@
subdir = subdirBuf.String()
buf.WriteString(fmt.Sprintf("@Subdir %s\n", subdir))
// Write package version line to cipd declaration
- buf.WriteString(fmt.Sprintf("%s %s\n", pkg.Name, pkg.Version))
+ plats, err := p.GetPlatforms()
+ if err != nil {
+ return "", err
+ }
+ cipdPath, err := cipd.Decl(p.Name, plats)
+ if err != nil {
+ return "", err
+ }
+ buf.WriteString(fmt.Sprintf("%s %s\n", cipdPath, p.Version))
return buf.String(), nil
}