You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2016/03/02 05:27:04 UTC

[3/3] incubator-mynewt-newt git commit: (MYNEWT-141) Replace SQLite targets with yml files.

(MYNEWT-141) Replace SQLite targets with yml files.


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/commit/1d8f746f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/tree/1d8f746f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/diff/1d8f746f

Branch: refs/heads/develop
Commit: 1d8f746f58fd81a5d975a6ce7319dd45cc2ba848
Parents: 9305e8e
Author: Christopher Collins <cc...@gmail.com>
Authored: Mon Feb 29 18:24:09 2016 -0800
Committer: Christopher Collins <cc...@gmail.com>
Committed: Tue Mar 1 20:21:04 2016 -0800

----------------------------------------------------------------------
 newt/cli/legacy.go | 109 ++++++++++++++++++++++++
 newt/cli/repo.go   | 219 ++++++++++--------------------------------------
 newt/cli/target.go | 180 ++++++++++++++++++++++-----------------
 newt/newt.go       |  57 ++++++++-----
 4 files changed, 291 insertions(+), 274 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1d8f746f/newt/cli/legacy.go
----------------------------------------------------------------------
diff --git a/newt/cli/legacy.go b/newt/cli/legacy.go
new file mode 100644
index 0000000..bdb7303
--- /dev/null
+++ b/newt/cli/legacy.go
@@ -0,0 +1,109 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 cli
+
+import (
+	"database/sql"
+	_ "github.com/mattn/go-sqlite3"
+	"log"
+	"os"
+	"strings"
+)
+
+// Migrates target definitions from the legacy sqlite database to
+// "targets/<target-name>/pkg.yml".  If a target is already in the new format,
+// it is not modified.  On success, the legacy database is renamed to prevent
+// subsequent migrations.  The database file is renamed as follows:
+//     .app/app.db --> .app/legacy-app.db
+//
+// @return                      true if any targets were migrated.
+func LegacyMigrateTargets(repo *Repo) bool {
+	const TARGET_SECT_PREFIX = "_target_"
+
+	dbName := repo.StorePath + "/app.db"
+	db, err := sql.Open("sqlite3", dbName)
+	if err != nil {
+		return false
+	}
+
+	// Populate repo configuration
+	log.Printf("[DEBUG] Reading legacy repo configuration from %s", dbName)
+	rows, err := db.Query("SELECT cfg_name,key,value FROM newt_cfg")
+	if err != nil {
+		return false
+	}
+	defer rows.Close()
+
+	// Copy all target settings from the database into the contents map.
+	contents := map[string]map[string]string{}
+	for rows.Next() {
+		var cfgName sql.NullString
+		var cfgKey sql.NullString
+		var cfgVal sql.NullString
+
+		err := rows.Scan(&cfgName, &cfgKey, &cfgVal)
+		if err != nil {
+			return false
+		}
+
+		if strings.HasPrefix(cfgName.String, TARGET_SECT_PREFIX) {
+			mapping, ok := contents[cfgName.String]
+			if !ok {
+				mapping = make(map[string]string)
+				contents[cfgName.String] = mapping
+			}
+
+			mapping[cfgKey.String] = cfgVal.String
+		}
+	}
+
+	// Iterate through the contents map, populating the target store with each
+	// target that doesn't already exist.
+	anyMigrated := false
+	for prefixedName, mapping := range contents {
+		name := prefixedName[len(TARGET_SECT_PREFIX):len(prefixedName)]
+
+		if !TargetExists(repo, name) {
+			target := NewTarget(repo, name)
+			for k, v := range mapping {
+				log.Printf("[DEBUG] Migrating legacy target %s, key=%s val=%s",
+					name, k, v)
+				target.Vars[k] = v
+			}
+
+			err := target.Save()
+			if err != nil {
+				return anyMigrated
+			}
+
+			anyMigrated = true
+		}
+	}
+
+	newName := repo.StorePath + "/legacy-app.db"
+	log.Printf("[DEBUG] Renaming legacy database: %s --> %s", dbName, newName)
+	err = os.Rename(dbName, newName)
+	if err != nil {
+		StatusMessage(VERBOSITY_QUIET, "Warning: Failed to rename legacy "+
+			"database (%s --> %s); %s\n", dbName, newName, err.Error())
+	}
+
+	return anyMigrated
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1d8f746f/newt/cli/repo.go
----------------------------------------------------------------------
diff --git a/newt/cli/repo.go b/newt/cli/repo.go
index a5066b4..fb286bd 100644
--- a/newt/cli/repo.go
+++ b/newt/cli/repo.go
@@ -20,15 +20,12 @@
 package cli
 
 import (
-	"database/sql"
 	"fmt"
 	"io/ioutil"
 	"log"
 	"os"
 	"path"
 	"path/filepath"
-
-	_ "github.com/mattn/go-sqlite3"
 )
 
 type Repo struct {
@@ -52,11 +49,8 @@ type Repo struct {
 
 	AddlPackagePaths []string
 
-	// Configuration
-	Config map[string]map[string]string
-
-	// The database handle for the repo configuration database
-	db *sql.DB
+	// Targets
+	Targets map[string]map[string]string
 }
 
 // Create a new Repo object and initialize it
@@ -165,190 +159,69 @@ func (repo *Repo) getRepoFile() (string, error) {
 	return rFile, err
 }
 
-// Create the contents of the configuration database
-func (repo *Repo) createDb(db *sql.DB) error {
-	query := `
-	CREATE TABLE IF NOT EXISTS newt_cfg (
-		cfg_name VARCHAR(255) NOT NULL,
-		key VARCHAR(255) NOT NULL,
-		value TEXT
-	)
-	`
-	_, err := db.Exec(query)
-	if err != nil {
-		return NewNewtError(err.Error())
-	} else {
-		return nil
-	}
+func (repo *Repo) TargetDir(targetName string) string {
+	return repo.BasePath + "/targets/" + targetName
 }
 
-// Initialize the configuration database specified by dbName.  If the database
-// doesn't exist, create it.
-func (repo *Repo) initDb(dbName string) error {
-	db, err := sql.Open("sqlite3", dbName)
+// Loads a single target definition residing in the specified directory.  The
+// path must contain a pkg.yml file at the top level to be a valid target
+// definion.
+func (repo *Repo) loadTarget(path string) error {
+	v, err := ReadConfig(path, "pkg")
 	if err != nil {
 		return err
 	}
-	repo.db = db
-
-	err = repo.createDb(db)
-	if err != nil {
-		return err
-	}
-
-	// Populate repo configuration
-	log.Printf("[DEBUG] Populating Repo configuration from %s", dbName)
-
-	rows, err := db.Query("SELECT * FROM newt_cfg")
-	if err != nil {
-		return NewNewtError(err.Error())
-	}
-	defer rows.Close()
-
-	for rows.Next() {
-		var cfgName sql.NullString
-		var cfgKey sql.NullString
-		var cfgVal sql.NullString
 
-		err := rows.Scan(&cfgName, &cfgKey, &cfgVal)
-		if err != nil {
-			return NewNewtError(err.Error())
-		}
-
-		log.Printf("[DEBUG] Setting sect %s, key %s to val %s", cfgName.String,
-			cfgKey.String, cfgVal.String)
-
-		_, ok := repo.Config[cfgName.String]
-		if !ok {
-			repo.Config[cfgName.String] = make(map[string]string)
-		}
-
-		repo.Config[cfgName.String][cfgKey.String] = cfgVal.String
-	}
-
-	return nil
-}
-
-// Get a configuration variable in section sect, with key
-// error is populated if variable doesn't exist
-func (repo *Repo) GetConfig(sect string, key string) (string, error) {
-	sectMap, ok := repo.Config[sect]
+	targetName := filepath.Base(path)
+	targetMap, ok := repo.Targets[targetName]
 	if !ok {
-		return "", NewNewtError("No configuration section exists")
+		targetMap = make(map[string]string)
+		repo.Targets[targetName] = targetMap
 	}
-
-	val, ok := sectMap[key]
-	if !ok {
-		return "", NewNewtError("No configuration variable exists")
+	settings := v.AllSettings()
+	for k, v := range settings {
+		targetMap[k] = v.(string)
 	}
 
-	return val, nil
-}
-
-func (repo *Repo) GetConfigSect(sect string) (map[string]string, error) {
-	sm, ok := repo.Config[sect]
-	if !ok {
-		return nil, NewNewtError("No configuration section exists")
-	}
-
-	return sm, nil
+	return nil
 }
 
-// Delete a configuration variable in section sect with key and val
-// Returns an error if configuration variable cannot be deleted
-// (most likely due to database error or key not existing)
-func (repo *Repo) DelConfig(sect string, key string) error {
-	db := repo.db
-
-	log.Printf("[DEBUG] Deleting sect %s, key %s", sect, key)
-
-	tx, err := db.Begin()
-	if err != nil {
+// Loads all target definitions rooted at the specified path.  Targets have the
+// following file structure:
+//     <path>/<target-name>/pkg.yml
+func (repo *Repo) loadPath(path string) error {
+	targetDirList, err := ioutil.ReadDir(path)
+	if err != nil && !os.IsNotExist(err) {
 		return NewNewtError(err.Error())
 	}
 
-	stmt, err := tx.Prepare("DELETE FROM newt_cfg WHERE cfg_name=? AND key=?")
-	if err != nil {
-		return err
-	}
-	defer stmt.Close()
-
-	res, err := stmt.Exec(sect, key)
-	if err != nil {
-		return err
-	}
+	for _, node := range targetDirList {
+		name := node.Name()
+		if node.IsDir() &&
+			!filepath.HasPrefix(name, ".") &&
+			!filepath.HasPrefix(name, "..") {
 
-	tx.Commit()
+			fullPath := path + name + "/"
 
-	if affected, err := res.RowsAffected(); affected > 0 && err == nil {
-		log.Printf("[DEBUG] sect %s, key %s successfully deleted from database",
-			sect, key)
-	} else {
-		log.Printf("[DEBUG] sect %s, key %s not found, considering \"delete\" successful",
-			sect, key)
+			err = repo.loadTarget(fullPath)
+			if err != nil {
+				return err
+			}
+		}
 	}
 
 	return nil
 }
 
-// Set a configuration variable in section sect with key, and val
-// Returns an error if configuration variable cannot be set
-// (most likely not able to set it in database.)
-func (repo *Repo) SetConfig(sect string, key string, val string) error {
-	_, ok := repo.Config[sect]
+// Retrieves the key-value variable map for the target with the specified name.
+// error is populated if variable doesn't exist
+func (repo *Repo) GetTargetVars(name string) (map[string]string, error) {
+	targetVars, ok := repo.Targets[name]
 	if !ok {
-		repo.Config[sect] = make(map[string]string)
-	}
-	repo.Config[sect][key] = val
-
-	// Store config
-	log.Printf("[DEBUG] Storing value %s into key %s for section %s",
-		val, sect, key)
-	db := repo.db
-
-	tx, err := db.Begin()
-	if err != nil {
-		return NewNewtError(err.Error())
-	}
-
-	stmt, err := tx.Prepare(
-		"UPDATE newt_cfg SET value=? WHERE cfg_name=? AND key=?")
-	if err != nil {
-		return NewNewtError(err.Error())
+		return nil, NewNewtError(fmt.Sprintf("Target not found: %s", name))
 	}
-	defer stmt.Close()
 
-	res, err := stmt.Exec(val, sect, key)
-	if err != nil {
-		return NewNewtError(err.Error())
-	}
-
-	// Value already existed, and we updated it.  Mission accomplished!
-	// Exit
-	if affected, err := res.RowsAffected(); affected > 0 && err == nil {
-		tx.Commit()
-		log.Printf("[DEBUG] Key %s, sect %s successfully updated to %s", key, sect, val)
-		return nil
-	}
-
-	// Otherwise, insert a new row
-	stmt1, err := tx.Prepare("INSERT INTO newt_cfg VALUES (?, ?, ?)")
-	if err != nil {
-		return NewNewtError(err.Error())
-	}
-	defer stmt1.Close()
-
-	_, err = stmt1.Exec(sect, key, val)
-	if err != nil {
-		return NewNewtError(err.Error())
-	}
-
-	tx.Commit()
-
-	log.Printf("[DEBUG] Key %s, sect %s successfully create, value set to %s",
-		key, sect, val)
-
-	return nil
+	return targetVars, nil
 }
 
 func (repo *Repo) PkgPaths() []string {
@@ -460,7 +333,7 @@ func (repo *Repo) Init() error {
 		return err
 	}
 
-	log.Printf("[DEBUG] Configuration loaded!  Initializing .app database")
+	log.Printf("[DEBUG] Configuration loaded")
 
 	// Create Repo store directory
 	repo.StorePath = repo.BasePath + "/.app/"
@@ -470,16 +343,12 @@ func (repo *Repo) Init() error {
 		}
 	}
 
-	// Create Repo configuration database
-	repo.Config = make(map[string]map[string]string)
-
-	dbName := repo.StorePath + "/app.db"
-	if err := repo.initDb(dbName); err != nil {
+	// Load target YAML files.
+	repo.Targets = make(map[string]map[string]string)
+	if err := repo.loadPath(repo.BasePath + "/targets/"); err != nil {
 		return err
 	}
 
-	log.Printf("[DEBUG] Database initialized.")
-
 	// Load PkgLists for the current Repo
 	repo.PkgListPath = repo.StorePath + "/pkg-lists/"
 	if NodeNotExist(repo.PkgListPath) {

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1d8f746f/newt/cli/target.go
----------------------------------------------------------------------
diff --git a/newt/cli/target.go b/newt/cli/target.go
index 8b026a3..5b4ee3c 100644
--- a/newt/cli/target.go
+++ b/newt/cli/target.go
@@ -22,6 +22,7 @@ package cli
 import (
 	"bufio"
 	"fmt"
+	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -30,8 +31,6 @@ import (
 	"strings"
 )
 
-const TARGET_SECT_PREFIX = "_target_"
-
 type Target struct {
 	Vars map[string]string
 
@@ -55,40 +54,44 @@ type Target struct {
 	Repo *Repo
 }
 
+func NewTarget(repo *Repo, name string) *Target {
+	target := Target{
+		Repo: repo,
+		Name: name,
+		Vars: map[string]string{
+			"name":         name,
+			"vers":         "0.0.1",
+			"arch":         "sim",
+			"compiler_def": "default",
+		},
+	}
+
+	return &target
+}
+
 // Check if the target specified by name exists for the Repo specified by
 // r
 func TargetExists(repo *Repo, name string) bool {
-	_, err := repo.GetConfig(TARGET_SECT_PREFIX+name, "name")
-	if err == nil {
-		return true
-	} else {
-		return false
-	}
+	_, err := repo.GetTargetVars(name)
+	return err == nil
 }
 
 func parseTargetStringSlice(str string) ([]string, error) {
-	slice := strings.Split(str, " ")
+	var slice []string
+	if str != "" {
+		slice = strings.Split(str, " ")
+	}
 	return slice, nil
 }
 
-func (t *Target) SetDefaults() error {
+// Migrates some special key-value pairs from the target's Vars map to named
+// struct fields.  This function should be called any time a target is
+// modified.
+func (t *Target) populateFields() error {
 	var err error
 
-	t.Name = t.Vars["name"]
-
-	// Must have an architecture set, default to sim.
-	if t.Vars["arch"] == "" {
-		t.Vars["arch"] = "sim"
-		t.Arch = "sim"
-	} else {
-		t.Arch = t.Vars["arch"]
-	}
-
+	t.Arch = t.Vars["arch"]
 	t.Cdef = t.Vars["compiler_def"]
-	if t.Cdef == "" {
-		t.Cdef = "default"
-	}
-
 	t.Bsp = t.Vars["bsp"]
 	t.Cflags = t.Vars["cflags"]
 	t.Lflags = t.Vars["lflags"]
@@ -99,7 +102,8 @@ func (t *Target) SetDefaults() error {
 	}
 	t.Identities = map[string]string{}
 	for _, ident := range identities {
-		StatusMessage(VERBOSITY_VERBOSE, "  set default ident %s\n", ident)
+		StatusMessage(VERBOSITY_VERBOSE, "Setting identity; target=%s "+
+			"identity=%s\n", t.Name, ident)
 		t.Identities[ident] = t.Name
 	}
 	t.Capabilities, err = parseTargetStringSlice(t.Vars["capabilities"])
@@ -115,6 +119,11 @@ func (t *Target) SetDefaults() error {
 	return nil
 }
 
+func (t *Target) SetVar(key string, value string) error {
+	t.Vars[key] = value
+	return t.populateFields()
+}
+
 func (t *Target) HasIdentity(identity string) bool {
 	for cur, _ := range t.Identities {
 		if cur == identity {
@@ -127,19 +136,21 @@ func (t *Target) HasIdentity(identity string) bool {
 
 // Load the target specified by name for the repository specified by r
 func LoadTarget(repo *Repo, name string) (*Target, error) {
-	t := &Target{
-		Repo: repo,
-	}
+	t := NewTarget(repo, name)
 
 	var err error
-
-	t.Vars, err = repo.GetConfigSect(TARGET_SECT_PREFIX + name)
+	vars, err := repo.GetTargetVars(name)
 	if err != nil {
 		return nil, err
 	}
 
-	// Cannot have both a project and package set
-	err = t.SetDefaults()
+	for k, v := range vars {
+		log.Printf("[DEBUG] Setting target %s, key=%s val=%s", name, k, v)
+		t.Vars[strings.TrimPrefix(k, "pkg.")] = v
+	}
+
+	// Copy special key-value pairs into their corresponding target fields.
+	err = t.populateFields()
 	if err != nil {
 		return nil, err
 	}
@@ -147,8 +158,9 @@ func LoadTarget(repo *Repo, name string) (*Target, error) {
 	return t, nil
 }
 
-// Export a target, or all targets.  If exportAll is true, then all targets are exported, if false,
-// then only the target represented by targetName is exported
+// Export a target, or all targets.  If exportAll is true, then all targets are
+// exported, if false, then only the target represented by targetName is
+// exported.
 func ExportTargets(repo *Repo, name string, exportAll bool, fp *os.File) error {
 	targets, err := GetTargets(repo)
 	if err != nil {
@@ -200,7 +212,8 @@ func ImportTargets(repo *Repo, name string, importAll bool, fp *os.File) error {
 				currentTarget = nil
 			}
 
-			// look either for an end of target definitions, or a new target definition
+			// look either for an end of target definitions, or a new target
+			// definition
 			if line == "@endtargets" {
 				break
 			} else {
@@ -209,17 +222,7 @@ func ImportTargets(repo *Repo, name string, importAll bool, fp *os.File) error {
 
 				if importAll || elements[1] == name {
 					// create a current target
-					currentTarget = &Target{
-						Repo: repo,
-					}
-
-					var err error
-					currentTarget.Vars = map[string]string{}
-					if err != nil {
-						return err
-					}
-
-					currentTarget.Vars["name"] = elements[1]
+					currentTarget = NewTarget(repo, elements[1])
 				}
 			}
 		} else {
@@ -236,10 +239,6 @@ func ImportTargets(repo *Repo, name string, importAll bool, fp *os.File) error {
 	}
 
 	for _, target := range targets {
-		if err := target.SetDefaults(); err != nil {
-			return err
-		}
-
 		if err := target.Save(); err != nil {
 			return err
 		}
@@ -251,15 +250,13 @@ func ImportTargets(repo *Repo, name string, importAll bool, fp *os.File) error {
 // Get a list of targets for the repository specified by r
 func GetTargets(repo *Repo) ([]*Target, error) {
 	targets := []*Target{}
-	for sect, _ := range repo.Config {
-		if strings.HasPrefix(sect, TARGET_SECT_PREFIX) {
-			target, err := LoadTarget(repo, sect[len(TARGET_SECT_PREFIX):len(sect)])
-			if err != nil {
-				return nil, err
-			}
-
-			targets = append(targets, target)
+	for targetName, _ := range repo.Targets {
+		target, err := LoadTarget(repo, targetName)
+		if err != nil {
+			return nil, err
 		}
+
+		targets = append(targets, target)
 	}
 	return targets, nil
 }
@@ -357,11 +354,14 @@ func (t *Target) Test(cmd string, flag bool) error {
 	return nil
 }
 
-func (t *Target) DeleteVar(name string) error {
-	targetCfgSect := TARGET_SECT_PREFIX + t.Vars["name"]
+func (t *Target) DeleteVar(varName string) error {
+	_, exists := t.Vars[varName]
 
-	if err := t.Repo.DelConfig(targetCfgSect, name); err != nil {
-		return err
+	if exists {
+		delete(t.Vars, varName)
+		if err := t.Save(); err != nil {
+			return err
+		}
 	}
 
 	return nil
@@ -369,36 +369,58 @@ func (t *Target) DeleteVar(name string) error {
 
 // Save the target's configuration elements
 func (t *Target) Save() error {
-	repo := t.Repo
+	dirpath := t.Repo.TargetDir(t.Name)
+	if err := os.MkdirAll(dirpath, 0755); err != nil {
+		return NewNewtError(err.Error())
+	}
 
-	if _, ok := t.Vars["name"]; !ok {
-		return NewNewtError("Cannot save a target without a name")
+	filepath := dirpath + "/pkg.yml"
+	file, err := os.Create(filepath)
+	if err != nil {
+		return NewNewtError(err.Error())
 	}
+	defer file.Close()
 
-	targetCfg := TARGET_SECT_PREFIX + t.Vars["name"]
+	file.WriteString("### Target: " + t.Name + "\n\n")
 
-	for k, v := range t.Vars {
-		if err := repo.SetConfig(targetCfg, k, v); err != nil {
-			return err
-		}
+	keys := []string{}
+	for k, _ := range t.Vars {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
+	for _, k := range keys {
+		file.WriteString("pkg." + k + ": " + t.Vars[k] + "\n")
 	}
 
 	return nil
 }
 
-func (t *Target) Remove() error {
-	repo := t.Repo
+// Tells you if the target's directory contains extra user files (i.e., files
+// other than pkg.yml).
+func (t *Target) ContainsUserFiles() (bool, error) {
+	path := t.Repo.TargetDir(t.Name)
+	contents, err := ioutil.ReadDir(path)
+	if err != nil {
+		return false, err
+	}
 
-	if _, ok := t.Vars["name"]; !ok {
-		return NewNewtError("Cannot remove a target without a name")
+	userFiles := false
+	for _, node := range contents {
+		name := node.Name()
+		if name != "." && name != ".." && name != "yml" {
+			userFiles = true
+			break
+		}
 	}
 
-	cfgSect := TARGET_SECT_PREFIX + t.Vars["name"]
+	return userFiles, nil
+}
 
-	for k, _ := range t.Vars {
-		if err := repo.DelConfig(cfgSect, k); err != nil {
-			return err
-		}
+func (t *Target) Remove() error {
+	path := t.Repo.TargetDir(t.Name)
+	if err := os.RemoveAll(path); err != nil {
+		return NewNewtError(err.Error())
 	}
 
 	return nil

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/1d8f746f/newt/newt.go
----------------------------------------------------------------------
diff --git a/newt/newt.go b/newt/newt.go
index ace82a4..d64b9ee 100644
--- a/newt/newt.go
+++ b/newt/newt.go
@@ -20,6 +20,7 @@
 package main
 
 import (
+	"bufio"
 	"fmt"
 	"log"
 	"os"
@@ -41,6 +42,7 @@ var NewtRepo *cli.Repo
 var newtSilent bool
 var newtQuiet bool
 var newtVerbose bool
+var newtForce bool
 var AutoTargets string = "autotargets"
 var NewtBranchPkgList string
 var NewtBranchPkg string
@@ -132,7 +134,7 @@ func targetSetCmd(cmd *cobra.Command, args []string) {
 		return
 	} else {
 		// Assign value to specified variable.
-		t.Vars[ar[0]] = ar[1]
+		t.SetVar(ar[0], ar[1])
 	}
 
 	if err := t.Save(); err != nil {
@@ -167,12 +169,12 @@ type ByName []*cli.Target
 
 func (a ByName) Len() int           { return len(a) }
 func (a ByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
-func (a ByName) Less(i, j int) bool { return a[i].Vars["name"] < a[j].Vars["name"] }
+func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
 
 func targetShowCmd(cmd *cobra.Command, args []string) {
-	dispSect := ""
+	dispTarget := ""
 	if len(args) == 1 {
-		dispSect = args[0]
+		dispTarget = args[0]
 	}
 
 	targets, err := cli.GetTargets(NewtRepo)
@@ -183,8 +185,8 @@ func targetShowCmd(cmd *cobra.Command, args []string) {
 	sort.Sort(ByName(targets))
 
 	for _, target := range targets {
-		if dispSect == "" || dispSect == target.Vars["name"] {
-			cli.StatusMessage(cli.VERBOSITY_QUIET, target.Vars["name"]+"\n")
+		if dispTarget == "" || dispTarget == target.Name {
+			cli.StatusMessage(cli.VERBOSITY_QUIET, target.Name+"\n")
 
 			vars := target.GetVars()
 			var keys []string
@@ -212,11 +214,7 @@ func targetCreateCmd(cmd *cobra.Command, args []string) {
 			"Target already exists, cannot create target with same name."))
 	}
 
-	target := &cli.Target{
-		Repo: NewtRepo,
-		Vars: map[string]string{},
-	}
-	target.Vars["name"] = args[0]
+	target := cli.NewTarget(NewtRepo, args[0])
 
 	err := target.Save()
 	if err != nil {
@@ -272,6 +270,25 @@ func targetDelCmd(cmd *cobra.Command, args []string) {
 		NewtUsage(cmd, err)
 	}
 
+	if !newtForce {
+		// Determine if the target directory contains extra user files.  If it
+		// does, a prompt (or force) is required to delete it.
+		userFiles, err := t.ContainsUserFiles()
+		if err != nil {
+			NewtUsage(cmd, err)
+		}
+
+		if userFiles {
+			scanner := bufio.NewScanner(os.Stdin)
+			fmt.Printf("Target directory %s contains some extra content; "+
+				"delete anyway? (y/N): ", t.Name)
+			rc := scanner.Scan()
+			if !rc || strings.ToLower(scanner.Text()) != "y" {
+				return
+			}
+		}
+	}
+
 	// Clean target prior to deletion; ignore errors during clean.
 	t.BuildClean(false)
 
@@ -460,19 +477,17 @@ func targetAddCmds(base *cobra.Command) {
 				NewtUsage(nil, err)
 			}
 
-			file, err := os.Open(NewtRepo.BasePath + "/" + AutoTargets)
-			if err == nil {
-				err = cli.ImportTargets(NewtRepo, "", true, file)
+			// Try to migrate targets from legacy sqlite database.  If any
+			// targets are migrated, recreate the repo from the updated targets
+			// directory.
+			if cli.LegacyMigrateTargets(NewtRepo) {
+				NewtRepo, err = cli.NewRepo()
 				if err != nil {
-					log.Printf("[DEBUG] failed to import automatic "+
-						"targets %s", err.Error())
+					NewtUsage(nil, err)
 				}
-				file.Close()
-			} else {
-				log.Printf("[DEBUG] failed to import automatic "+
-					"targets %s", err.Error())
 			}
 		},
+
 		Run: func(cmd *cobra.Command, args []string) {
 			cmd.Usage()
 		},
@@ -1455,6 +1470,8 @@ func parseCmds() *cobra.Command {
 		"Be silent; don't output anything.")
 	newtCmd.PersistentFlags().StringVarP(&NewtLogLevel, "loglevel", "l",
 		"WARN", "Log level, defaults to WARN.")
+	newtCmd.PersistentFlags().BoolVarP(&newtForce, "force", "f", false,
+		"Perform operations without prompting for confirmation.")
 
 	versHelpText := formatHelp(`Display the Newt version number.`)
 	versHelpEx := "  newt version"