Repository: conda-forge/conda-smithy

Issue:
Overriding exclusive_config_file
Raising this to discuss which option we prefer in terms of setting `exclusive_config_file`. Should it be a `conda-smithy` CLI option or should it live in `conda-forge.yml`?


Discussion:
cc @msarahan @isuruf
You can't set it in conda_build_config.yaml.  Do you mean conda-forge.yml?
Thanks Mike. Yes, I meant `conda-forge.yml`. Have edited it above. Sorry for the typo.
I think it depends on how much you can get away with relative paths.  If you point at a file within the feedstock folder, it can live in conda-forge.yml.  If you point outside of the feedstock folder, it needs to be a CLI option.
> If you point outside of the feedstock folder, it needs to be a CLI option.

In #652, if you give the absolute path or a relative path with respect to the feedstock folder it works.

I prefer the `conda-forge.yml`, because if somebody changes `conda-forge.yml` we can see the change. If they don't commit, we won't, but it's better than CLI.

We want to discourage files outside the feedstock anyway.
I think I did a bad job of motivating this discussion. Perhaps we should give some thought to when this feature will be used and then the right option will be clear to us.

The main use cases I can think of are testing/development, maybe to workaround bad behavior in `conda-smithy` or the pinning file, etc. Any others?
> The main use cases I can think of are testing/development, maybe to workaround bad behavior in conda-smithy or the pinning file, etc. Any others?

I'm not sure I understand the use case, but I guess we can remove this from 3.0.0 milestone right?
My point is I'm not sure what use case we have to override the pinning file currently. I've speculated on a few. However if there is no such use case for changing the pinning file used, we shouldn't provide an option. Otherwise we are just adding maintenance effort for no purpose, right? Plus we will be limiting our ability to make a more informed choice down the road when use cases actually crop up.

Regardless this is still part of the 3.0.0 milestone as we are on the hook for any option we provide here. So we should make a decision about what if any option is appropriate. Based on your recent comment, I'm leaning towards not allow the pinning file to be configurable. That way we can give more thought to how it can be configured later.
I forgot the use case of conda-smithy itself in the tests. I think making the tests independent of `conda-forge-pinning` is nice, so that we don't have to update tests when `conda-forge-pinning` changes.

That's the only use case I can think of. In all other use-cases people can override `conda-forge-pinning` instead of replacing it.
Right, it seems to me that it would be a little less awkward to handle with a CLI option than editing `conda-forge.yaml`. Namely we can easily try multiple different configs with the same recipe using a CLI option in the tests, but would need to edit or keep multiple copies of the recipe if using `conda-forge.yml`.

That said, I don't have strong feelings on this. So if you feel strongly about keeping this setting in `conda-forge.yml`, maybe we should just leave it as is.
> Right, it seems to me that it would be a little less awkward to handle with a CLI option than editing `conda-forge.yaml`

Sure. CLI option is okay as well.

Resolving patch:
diff --git a/conda_smithy/cli.py b/conda_smithy/cli.py
--- a/conda_smithy/cli.py
+++ b/conda_smithy/cli.py
@@ -180,11 +180,15 @@ def __init__(self, parser):
                          help="Whether to setup a commit or not.")
         scp.add_argument("--no-check-uptodate", default=False,
                          help="Don't check that conda-smithy and conda-forge-pinning are uptodate")
+        scp.add_argument("-e", "--exclusive-config-file", default=None,
+                         help="Exclusive conda-build config file to replace conda-forge-pinning. " + \
+                              "For advanced usage only")
 
     def __call__(self, args):
         try:
             configure_feedstock.main(args.feedstock_directory,
-                                     no_check_uptodate=args.no_check_uptodate, commit=args.commit)
+                                     no_check_uptodate=args.no_check_uptodate, commit=args.commit,
+                                     exclusive_config_file=args.exclusive_config_file)
         except RuntimeError as e:
             print(e)
         except subprocess.CalledProcessError as e:
diff --git a/conda_smithy/configure_feedstock.py b/conda_smithy/configure_feedstock.py
--- a/conda_smithy/configure_feedstock.py
+++ b/conda_smithy/configure_feedstock.py
@@ -557,7 +557,6 @@ def _load_forge_config(forge_dir, exclusive_config_file):
               'travis': {},
               'circle': {},
               'appveyor': {},
-              'exclusive_config_file': exclusive_config_file,
               'channels': {'sources': ['conda-forge', 'defaults'],
                            'targets': [['conda-forge', 'main']]},
               'github': {'user_or_org': 'conda-forge', 'repo_name': ''},
@@ -599,6 +598,7 @@ def _load_forge_config(forge_dir, exclusive_config_file):
         if not feedstock_name.endswith("-feedstock"):
             feedstock_name += "-feedstock"
         config['github']['repo_name'] = feedstock_name
+    config['exclusive_config_file'] = exclusive_config_file
     return config
 
 
@@ -663,7 +663,7 @@ def commit_changes(forge_file_directory, commit, cs_ver, cfp_ver):
             print("No changes made. This feedstock is up-to-date.\n")
 
 
-def main(forge_file_directory, no_check_uptodate, commit):
+def main(forge_file_directory, no_check_uptodate, commit, exclusive_config_file):
     error_on_warn = False if no_check_uptodate else True
     index = conda_build.conda_interface.get_index(channel_urls=['conda-forge']) 
     r = conda_build.conda_interface.Resolve(index)
@@ -673,10 +673,12 @@ def main(forge_file_directory, no_check_uptodate, commit):
 
     forge_dir = os.path.abspath(forge_file_directory)
 
-    config = _load_forge_config(forge_dir, None)
-    exclusive_config_file = config['exclusive_config_file']
-
-    if exclusive_config_file is None:
+    if exclusive_config_file is not None:
+        exclusive_config_file = os.path.join(forge_dir, exclusive_config_file)
+        if not os.path.exists(exclusive_config_file):
+            raise RuntimeError("Given exclusive-config-file not found.")
+        cf_pinning_ver = None
+    else:
         installed_vers = conda_build.conda_interface.get_installed_version(
                                 conda_build.conda_interface.root_dir, ["conda-forge-pinning"])
         cf_pinning_ver = installed_vers["conda-forge-pinning"]
@@ -687,12 +689,9 @@ def main(forge_file_directory, no_check_uptodate, commit):
         cf_pinning_file = os.path.join(conda_build.conda_interface.root_dir, "conda_build_config.yaml")
         if not os.path.exists(cf_pinning_file):
             raise RuntimeError("conda_build_config.yaml from conda-forge-pinning is missing")
-        config['exclusive_config_file'] = cf_pinning_file
-    else:
-        config['exclusive_config_file'] = os.path.join(forge_dir, exclusive_config_file)
-        if not os.path.exists(config['exclusive_config_file']):
-            raise RuntimeError("exclusive_config_file in conda-forge.yml is not found.")
-        cf_pinning_ver = None
+        exclusive_config_file = cf_pinning_file
+
+    config = _load_forge_config(forge_dir, exclusive_config_file)
 
     for each_ci in ["travis", "circle", "appveyor"]:
         if config[each_ci].pop("enabled", None):


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -14,7 +14,8 @@
 
 RegenerateArgs = collections.namedtuple('ArgsObject', ('commit',
                                                        'feedstock_directory',
-                                                       'no_check_uptodate'))
+                                                       'no_check_uptodate',
+                                                       'exclusive_config_file'))
 
 
 def test_init(py_recipe):
@@ -45,11 +46,10 @@ def test_init_multiple_output_matrix(testing_workdir):
     init_obj(args)
     # Ignore conda-forge-pinning for this test, as the test relies on conda-forge-pinning
     # not being present
-    with open(os.path.join(feedstock_dir, "conda-forge.yml"), "w") as f:
-        f.write("exclusive_config_file: recipe/conda_build_config.yaml")
     args = RegenerateArgs(feedstock_directory=feedstock_dir,
                           commit=False,
-                          no_check_uptodate=True)
+                          no_check_uptodate=True,
+                          exclusive_config_file="recipe/conda_build_config.yaml")
     regen_obj(args)
     matrix_dir = os.path.join(feedstock_dir, '.ci_support')
     # the matrix should be consolidated among all outputs, as well as the top-level
@@ -88,23 +88,23 @@ def test_regenerate(py_recipe, testing_workdir):
     # original rendering was with py27, 36, no target_platform
     assert len(os.listdir(matrix_folder)) == 7
 
-    dest_file = os.path.join(dest_dir, 'recipe', 'conda_build_config.yaml')
+    # Ignore conda-forge-pinning for this test, as the test relies on conda-forge-pinning
+    # not being present
     args = RegenerateArgs(feedstock_directory=dest_dir,
                           commit=False,
-                          no_check_uptodate=True)
+                          no_check_uptodate=True,
+                          exclusive_config_file=os.path.join(recipe, 'config.yaml'))
 
-    # Ignore conda-forge-pinning for this test, as the test relies on conda-forge-pinning
-    # not being present
-    with open(os.path.join(dest_dir, "conda-forge.yml"), "w") as f:
-        f.write("exclusive_config_file: {}".format(os.path.join(recipe, 'config.yaml')))
     regen_obj(args)
 
     # should add 2, as the config.yaml adds in target_platform
     assert len(os.listdir(matrix_folder)) == 9
 
     # reduce the python matrix and make sure the matrix files reflect the change
-    with open(os.path.join(dest_dir, "conda-forge.yml"), "w") as f:
-        f.write("exclusive_config_file: {}".format(os.path.join(recipe, 'short_config.yaml')))
+    args = RegenerateArgs(feedstock_directory=dest_dir,
+                          commit=False,
+                          no_check_uptodate=True,
+                          exclusive_config_file=os.path.join(recipe, 'short_config.yaml'))
     regen_obj(args)
 
     # one py ver, no target_platform  (tests that older configs don't stick around)