Check in manifest/ , a small library for reading and writing manifest
files.
Change-Id: Ifee6b01640197f9113d6845c980b1e80f0c5f8bf
diff --git a/manifest/parser.go b/manifest/parser.go
new file mode 100644
index 0000000..0bdde9b
--- /dev/null
+++ b/manifest/parser.go
@@ -0,0 +1,81 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// 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 manifest
+
+import (
+ "encoding/xml"
+ "io/ioutil"
+ "sort"
+ "strings"
+)
+
+func (p *Project) parse() {
+ for _, s := range strings.Split(p.GroupsString, ",") {
+ if s == "" {
+ continue
+ }
+ if p.Groups == nil {
+ p.Groups = map[string]bool{}
+ }
+ p.Groups[s] = true
+ }
+}
+
+func (p *Project) prepare() {
+ var keys []string
+ for k, v := range p.Groups {
+ if v {
+ keys = append(keys, k)
+ }
+ }
+
+ sort.Strings(keys)
+ p.GroupsString = strings.Join(keys, ",")
+}
+
+// Parse parses the given XML data.
+func Parse(contents []byte) (*Manifest, error) {
+ var m Manifest
+ if err := xml.Unmarshal(contents, &m); err != nil {
+ return nil, err
+ }
+
+ for i := range m.Project {
+ m.Project[i].parse()
+ }
+ return &m, nil
+}
+
+// MarshalXML serializes the receiver to XML.
+func (m *Manifest) MarshalXML() ([]byte, error) {
+ for i := range m.Project {
+ m.Project[i].prepare()
+ }
+
+ content, err := xml.MarshalIndent(m, "", " ")
+ if err != nil {
+ return nil, err
+ }
+ return content, nil
+}
+
+// ParseFile reads and parses an XML file
+func ParseFile(name string) (*Manifest, error) {
+ content, err := ioutil.ReadFile(name)
+ if err != nil {
+ return nil, err
+ }
+ return Parse(content)
+}
diff --git a/manifest/parser_test.go b/manifest/parser_test.go
new file mode 100644
index 0000000..0228487
--- /dev/null
+++ b/manifest/parser_test.go
@@ -0,0 +1,114 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// 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 manifest
+
+import (
+ "reflect"
+ "testing"
+)
+
+var aospManifest = `<?xml version="1.0" encoding="UTF-8"?>
+<manifest>
+ <remote name="aosp"
+ fetch=".."
+ review="https://android-review.googlesource.com/" />
+ <default revision="master"
+ remote="aosp"
+ sync-j="4" />
+
+ <project path="build" name="platform/build" groups="pdk,tradefed" >
+ <copyfile src="core/root.mk" dest="Makefile" />
+ </project>
+ <project path="build/soong" name="platform/build/soong" groups="pdk,tradefed" >
+ <linkfile src="root.bp" dest="Android.bp" />
+ </project>
+</manifest>`
+
+func TestBasic(t *testing.T) {
+ manifest, err := Parse([]byte(aospManifest))
+ if err != nil {
+ t.Fatalf("Unmarshal: %v", err)
+ }
+
+ want := &Manifest{
+ Remote: []Remote{{
+ Name: "aosp",
+ Fetch: "..",
+ Review: "https://android-review.googlesource.com/",
+ }},
+ Default: Default{
+ Revision: "master",
+ Remote: "aosp",
+ SyncJ: "4",
+ },
+ Project: []Project{
+ {
+ Path: "build",
+ Name: "platform/build",
+ GroupsString: "pdk,tradefed",
+ Groups: map[string]bool{
+ "pdk": true,
+ "tradefed": true,
+ },
+ Copyfile: []Copyfile{
+ {
+ Src: "core/root.mk",
+ Dest: "Makefile",
+ },
+ },
+ },
+ {
+ Path: "build/soong",
+ Name: "platform/build/soong",
+ GroupsString: "pdk,tradefed",
+ Groups: map[string]bool{
+ "pdk": true,
+ "tradefed": true,
+ },
+ Linkfile: []Linkfile{
+ {
+ Src: "root.bp",
+ Dest: "Android.bp",
+ },
+ },
+ },
+ },
+ }
+
+ if !reflect.DeepEqual(manifest, want) {
+ t.Errorf("got %v, want %v", manifest, want)
+ }
+}
+
+func TestRoundtrip(t *testing.T) {
+ manifest, err := Parse([]byte(aospManifest))
+ if err != nil {
+ t.Fatalf("Parse: %v", err)
+ }
+
+ xml, err := manifest.MarshalXML()
+ if err != nil {
+ t.Errorf("MarshalXML: %v", err)
+ }
+
+ roundtrip, err := Parse(xml)
+ if err != nil {
+ t.Errorf("Parse(roundtrip): %v", err)
+ }
+
+ if !reflect.DeepEqual(roundtrip, manifest) {
+ t.Errorf("got roundtrip %#v, want %#v", roundtrip, manifest)
+ }
+}
diff --git a/manifest/types.go b/manifest/types.go
new file mode 100644
index 0000000..343b42c
--- /dev/null
+++ b/manifest/types.go
@@ -0,0 +1,85 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// 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 manifest manipulates Manifest files as described at
+// https://gerrit.googlesource.com/git-repo/+/master/docs/manifest-format.txt.
+package manifest
+
+// Copyfile indicates that a file should be copied in a checkout
+type Copyfile struct {
+ Src string `xml:"src,attr"`
+ Dest string `xml:"dest,attr"`
+}
+
+// Linkfile indicates that a file should be symlinked in a checkout
+type Linkfile struct {
+ Src string `xml:"src,attr"`
+ Dest string `xml:"dest,attr"`
+}
+
+// Project represents a single git repository that should be stitched
+// into the checkout.
+type Project struct {
+ Path string `xml:"path,attr"`
+ Name string `xml:"name,attr"`
+ Remote string `xml:"remote,attr,omitempty"`
+ Copyfile []Copyfile `xml:"copyfile,omitempty"`
+ Linkfile []Linkfile `xml:"linkfile,omitempty"`
+ GroupsString string `xml:"groups,attr,omitempty"`
+ Groups map[string]bool `xml:"-"`
+
+ Revision string `xml:"revision,attr,omitempty"`
+ DestBranch string `xml:"dest-branch,attr,omitempty"`
+ SyncJ string `xml:"sync-j,attr,omitempty"`
+ SyncC string `xml:"sync-c,attr,omitempty"`
+ SyncS string `xml:"sync-s,attr,omitempty"`
+
+ Upstream string `xml:"upstream,attr,omitempty"`
+ CloneDepth string `xml:"clone-depth,attr,omitempty"`
+ ForcePath string `xml:"force-path,attr,omitempty"`
+
+ // This is not part of the Manifest spec.
+ CloneURL string `xml:"clone-url,attr,omitempty"`
+}
+
+// Remote describes a host where a set of projects is hosted.
+type Remote struct {
+ Alias string `xml:"alias,attr"`
+ Name string `xml:"name,attr"`
+ Fetch string `xml:"fetch,attr"`
+ Review string `xml:"review,attr"`
+ Revision string `xml:"revision,attr"`
+}
+
+// Default holds default Project settings.
+type Default struct {
+ Revision string `xml:"revision,attr"`
+ Remote string `xml:"remote,attr"`
+ DestBranch string `xml:"dest-branch,attr"`
+ SyncJ string `xml:"sync-j,attr"`
+ SyncC string `xml:"sync-c,attr"`
+ SyncS string `xml:"sync-s,attr"`
+}
+
+type ManifestServer struct {
+ URL string `xml:"url,attr"`
+}
+
+// Manifest holds the entire manifest, describing a set of git
+// projects to be stitched together
+type Manifest struct {
+ Default Default `xml:"default"`
+ Remote []Remote `xml:"remote"`
+ Project []Project `xml:"project"`
+}