Repository: catkin/catkin_tools

Issue:
catkin locate --build does not find 'profile'_build
[//]: # (Thank you for reporting an issue for catkin_tools!)

[//]: # (Please fill out the details below so that your issue can be resolved quickly!)
[//]: # (If you want to suggest a feature or start a discussion, feel free to delete them.)

### System Info

* Operating System: Ubuntu 14.04
* Python Version: Python 2.7.6
* Version of catkin_tools: catkin_tools 0.4.4
* ROS Distro: indigo

### Build / Run Issue

[//]: # (If you are migrating from catkin_make, please follow the migration guide:)
[//]: # (http://catkin-tools.readthedocs.org/en/latest/migration.html)

[//]: # (Please also check for solved issues here:)
[//]: # (http://catkin-tools.readthedocs.org/en/latest/troubleshooting.html)

[//]: # (And check for open issues here:)
[//]: # (https://github.com/catkin/catkin_tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug)

[//]: # (If you have an issue building and running code, please verify the following:)

* [ ] Works with `catkin_make`
* [ ] Works with `catkin_make_isolated --merge`
* [ ] Works with `catkin build`
* [ ] Works with `catkin build -p1`
* [x] I did  not `read this`

### Expected Behavior

[//]: # (Please describe what you expected or think should happen.)

Given the following workspace :  

```terminal
~/workspace$ l
build_release/  devel_release/  logs_release/  src/
```
I was expecting the command locate to return the following :  
```terminal
~/workspace$ catkin locate --workspace $(pwd) --build
/home/user/workspace/build_release
```

### Actual Behavior

[//]: # (Please describe what actually happens.)

[//]: # (Please put long CLI output into Gists: http://gist.github.com)
[//]: # (Use `VERBOSE=1 catkin build -p1 -v` for verbose output)
[//]: # (Please post `logs` directory if available.)

It returns the default `build` directory despite that it does not exists
```terminal
~/workspace$ catkin locate --workspace $(pwd) --build
/home/user/workspace/build
```

### Steps to Reproduce the Issue

[//]: # (Please give a series of command-line commands which cause the problem.)
[//]: # (Include a checkout URI for the workspace so that we can reproduce the issue.)
[//]: # (If we can't reproduce it on our own machines, we can't fix it!)

```bash
~$ mkdir -p dummy_ws/src && cd dummy_ws
~/dummy_ws$ catkin config --profile release -x _release --cmake-args -DCMAKE_BUILD_TYPE=Release
~/dummy_ws$ catkin build --profile release
~/dummy_ws$ catkin locate --workspace $(pwd) --build
/home/user/dummy_ws/build
```



Discussion:

Patch:
diff --git a/catkin_tools/context.py b/catkin_tools/context.py
index c6ac7c7..f035063 100644
--- a/catkin_tools/context.py
+++ b/catkin_tools/context.py
@@ -191,6 +191,8 @@ class Context(object):
 
         # Get the active profile
         profile = profile or opts_vars.get('profile', None) or metadata.get_active_profile(workspace)
+        if not metadata.active_profile_set(workspace):
+            metadata.set_active_profile(workspace, profile)
         opts_vars['profile'] = profile
 
         # Initialize empty metadata/args
diff --git a/catkin_tools/metadata.py b/catkin_tools/metadata.py
index 0545236..5337878 100644
--- a/catkin_tools/metadata.py
+++ b/catkin_tools/metadata.py
@@ -243,6 +243,7 @@ def init_metadata_root(workspace_path, reset=False):
     else:
         # Create a new .catkin_tools directory
         os.mkdir(metadata_root_path)
+        os.mkdir(os.path.join(metadata_root_path, 'profiles'))
 
     # Write the README file describing the directory
     with open(os.path.join(metadata_root_path, 'README'), 'w') as metadata_readme:
@@ -357,6 +358,12 @@ def get_active_profile(workspace_path):
     return DEFAULT_PROFILE_NAME
 
 
+def active_profile_set(workspace_path):
+    """Check if the active profile is set in profiles.yml"""
+    profiles_data = get_profiles_data(workspace_path)
+    return 'active' in profiles_data
+
+
 def get_profiles_data(workspace_path):
     """Get the contents of the profiles file.
 
diff --git a/catkin_tools/verbs/catkin_build/cli.py b/catkin_tools/verbs/catkin_build/cli.py
index c1ef811..99696ee 100644
--- a/catkin_tools/verbs/catkin_build/cli.py
+++ b/catkin_tools/verbs/catkin_build/cli.py
@@ -45,6 +45,7 @@ from catkin_tools.jobs.utils import CommandMissing
 from catkin_tools.jobs.utils import loadenv
 from catkin_tools.metadata import find_enclosing_workspace
 from catkin_tools.metadata import get_metadata
+from catkin_tools.metadata import init_metadata_root
 from catkin_tools.metadata import update_metadata
 from catkin_tools.resultspace import load_resultspace_environment
 from catkin_tools.terminal_color import set_color
@@ -252,14 +253,15 @@ def main(opts):
         sys.exit(clr("[build] @!@{rf}Error:@| With --no-deps, you must specify packages to build."))
 
     # Load the context
-    if opts.build_this or opts.start_with_this:
-        ctx = Context.load(opts.workspace, opts.profile, opts, append=True, strict=True)
-    else:
-        ctx = Context.load(opts.workspace, opts.profile, opts, append=True)
+    ctx = Context.load(opts.workspace, opts.profile, opts, append=True, strict=True)
 
     # Handle no workspace
-    if ctx is None:
+    if ctx is None and (opts.build_this or opts.start_with_this):
         sys.exit(clr("[build] @!@{rf}Error:@| The current folder is not part of a catkin workspace."))
+    elif ctx is None:
+        init_metadata_root(opts.workspace or os.getcwd())
+        ctx = Context.load(opts.workspace, opts.profile, opts, append=True)
+        log(clr('@!@{cf}Initialized new catkin workspace in `{}`@|').format(ctx.workspace))
 
     # Initialize the build configuration
     make_args, makeflags, cli_flags, jobserver = configure_make_args(
diff --git a/catkin_tools/verbs/catkin_config/cli.py b/catkin_tools/verbs/catkin_config/cli.py
index 4d46840..0fd729e 100644
--- a/catkin_tools/verbs/catkin_config/cli.py
+++ b/catkin_tools/verbs/catkin_config/cli.py
@@ -19,6 +19,7 @@ import sys
 from catkin_tools.argument_parsing import add_cmake_and_make_and_catkin_make_args
 from catkin_tools.argument_parsing import add_context_args
 from catkin_tools.context import Context
+from catkin_tools.metadata import init_metadata_root
 from catkin_tools.terminal_color import ColorMapper
 from catkin_tools.terminal_color import fmt
 
@@ -179,21 +180,35 @@ def main(opts):
             opts.profile,
             opts,
             append=opts.append_args,
-            remove=opts.remove_args)
+            remove=opts.remove_args,
+            strict=True)
 
         do_init = opts.init or not no_action
         summary_notes = []
 
-        if not context.initialized() and do_init:
-            summary_notes.append(clr('@!@{cf}Initialized new catkin workspace in `{}`@|').format(context.workspace))
+        if not context and not do_init:
+            # Don't initialize a new workspace
+            print(clr('@!@{rf}WARNING:@| Workspace is not yet initialized. '
+                      'Use catkin init or run catkin config with --init.'))
+
+        else:
+            # Either initialize it or it already exists
+            if not context:
+                init_metadata_root(opts.workspace or os.getcwd())
+                context = Context.load(
+                    opts.workspace,
+                    opts.profile,
+                    opts,
+                    append=opts.append_args,
+                    remove=opts.remove_args)
+                summary_notes.append(clr('@!@{cf}Initialized new catkin workspace in `{}`@|').format(context.workspace))
 
-        if context.initialized() or do_init:
             Context.save(context)
 
-        if opts.mkdirs and not context.source_space_exists():
-            os.makedirs(context.source_space_abs)
+            if opts.mkdirs and not context.source_space_exists():
+                os.makedirs(context.source_space_abs)
 
-        print(context.summary(notes=summary_notes))
+            print(context.summary(notes=summary_notes))
 
     except IOError as exc:
         # Usually happens if workspace is already underneath another catkin_tools workspace


Test patch:
diff --git a/tests/system/verbs/catkin_config/test_config.py b/tests/system/verbs/catkin_config/test_config.py
index 649d597..d214679 100644
--- a/tests/system/verbs/catkin_config/test_config.py
+++ b/tests/system/verbs/catkin_config/test_config.py
@@ -14,7 +14,7 @@ from ...workspace_factory import workspace_factory
 def test_config_no_ws():
     with redirected_stdio() as (out, err):
         assert catkin_success(['config'])
-        assert_warning_message(out, 'Workspace .+ is not yet initialized')
+        assert_warning_message(out, 'Workspace is not yet initialized')
 
 
 @in_temporary_directory
diff --git a/tests/system/verbs/catkin_profile/test_profile.py b/tests/system/verbs/catkin_profile/test_profile.py
index 480b6a7..f25c60a 100644
--- a/tests/system/verbs/catkin_profile/test_profile.py
+++ b/tests/system/verbs/catkin_profile/test_profile.py
@@ -1,3 +1,7 @@
+import os
+
+import yaml
+
 from ....utils import assert_cmd_success
 from ....utils import catkin_success
 from ....utils import in_temporary_directory
@@ -7,6 +11,18 @@ from ...workspace_factory import workspace_factory
 
 BUILD = ['build', '--no-notify', '--no-status']
 
+BUILD = ['build', '--no-notify', '--no-status']
+
+
+def assert_active_profile(workspace, profile):
+    profile_file = os.path.join(workspace, '.catkin_tools', 'profiles', 'profiles.yaml')
+    if not os.path.exists(profile_file):
+        assert profile == 'default'
+    else:
+        with open(profile_file) as f:
+            profiles = yaml.safe_load(f)
+        assert profiles.get('active', 'default') == profile
+
 
 @in_temporary_directory
 def test_profile_list():
@@ -15,6 +31,7 @@ def test_profile_list():
         assert catkin_success(['init'])
         assert catkin_success(BUILD)
         assert catkin_success(['profile', 'list'])
+    assert_active_profile('.', 'default')
 
 
 @in_temporary_directory
@@ -24,6 +41,10 @@ def test_profile_set():
         assert catkin_success(['init'])
         assert catkin_success(BUILD)
         assert catkin_success(['profile', 'set', 'default'])
+        assert catkin_success(['profile', 'add', 'second'])
+        assert_active_profile('.', 'default')
+        assert catkin_success(['profile', 'set', 'second'])
+        assert_active_profile('.', 'second')
 
 
 def test_profile_copy():
@@ -33,6 +54,7 @@ def test_profile_copy():
             assert catkin_success(['config', '--make-args', 'test'])
             assert catkin_success(['profile', 'add', '--copy', 'default', 'mycopy'])
         assert_in_config('.', 'mycopy', 'make_args', ['test'])
+        assert_active_profile('.', 'default')
 
 
 def test_profile_extend():
@@ -44,3 +66,10 @@ def test_profile_extend():
             assert catkin_success(['config', '--profile', 'myextend', '--skiplist', 'mypackage'])
         assert_in_config('.', 'default', 'make_args', ['test'])
         assert_in_config('.', 'myextend', 'blacklist', ['mypackage'])
+
+
+def test_different_first_profile():
+    with workspace_factory() as wf:
+        wf.build()
+        assert catkin_success(BUILD + ['--profile', 'release'])
+        assert_active_profile(wf.workspace, 'release')