Repository: ucbds-infra/otter-grader

Issue:
Add flags to disable auto-finding specific files
For example, if you're working in a directory with an `environment.yml` file but don't want Otter Generate to automatically include that in your zip file, there should be a flag to disable that behavior, e.g. `--no-env`. Or a flag that disables _all_ auto-finding.

Comments:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0256e918..945fbcb2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ _Changes listed in this section are currently hosted in the `beta` branch on Git
 * Added token argument to Otter Generate and Otter Assign
 * Implemented submission zip validation against local test cases in `otter.Notebook`
 * Removed metadata files for Otter Grade
+* Added flags for disabling file auto-inclusion in Otter Generate
 
 **v2.2.6:**
 
diff --git a/environment.yml b/environment.yml
index 6ae99487..d284731f 100644
--- a/environment.yml
+++ b/environment.yml
@@ -4,6 +4,8 @@ channels:
     - defaults
 dependencies:
     - python>=3.6
+    - gcc_linux-64
+    - gxx_linux-64
     - r-base
     - r-essentials
     - r-devtools
diff --git a/otter/assign/utils.py b/otter/assign/utils.py
index 988dc41c..e01bb8c6 100644
--- a/otter/assign/utils.py
+++ b/otter/assign/utils.py
@@ -275,7 +275,7 @@ def run_generate_autograder(result, assignment, gs_username, gs_password, plugin
         requirements=requirements,
         overwrite_requirements=assignment.overwrite_requirements,
         environment="environment.yml" if assignment.environment else None,
-        no_env=False,
+        no_environment=False,
         username=gs_username,
         password=gs_password,
         files=files,
diff --git a/otter/cli.py b/otter/cli.py
index 4f54028d..29a3bd0a 100644
--- a/otter/cli.py
+++ b/otter/cli.py
@@ -79,10 +79,12 @@ defaults = generate.__kwdefaults__
 @click.option("-t", "--tests-path", default=defaults["tests_path"], type=click.Path(exists=True, file_okay=False), help="Path to test files")
 @click.option("-o", "--output-dir", default=defaults["output_dir"], type=click.Path(exists=True, file_okay=False), help="Path to which to write zipfile")
 @click.option("-c", "--config", type=click.Path(exists=True, file_okay=False), help="Path to otter configuration file; ./otter_config.json automatically checked")
+@click.option("--no-config", is_flag=True, help="Disable auto-inclusion of unspecified Otter config file at ./otter_config.json")
 @click.option("-r", "--requirements", type=click.Path(exists=True, file_okay=False), help="Path to requirements.txt file; ./requirements.txt automatically checked")
+@click.option("--no-requirements", is_flag=True, help="Disable auto-inclusion of unespecified requirements file at ./requirements.txt")
 @click.option("--overwrite-requirements", is_flag=True, help="Overwrite (rather than append to) default requirements for Gradescope; ignored if no REQUIREMENTS argument")
 @click.option("-e", "--environment", type=click.Path(exists=True, file_okay=False), help="Path to environment.yml file; ./environment.yml automatically checked (overwrite)")
-@click.option("--no-env", is_flag=True, help="Whether to ignore an automatically found but unspecified environment.yml file")
+@click.option("--no-environment", is_flag=True, help="Disable auto-inclusion of unespecified environment file at ./environment.yml")
 @click.option("-l", "--lang", default=defaults["lang"], type=click.Choice(["python", "r"], case_sensitive=False), help="Assignment programming language; defaults to Python")
 @click.option("--username", help="Gradescope username for generating a token")
 @click.option("--password", help="Gradescope password for generating a token")
diff --git a/otter/generate/__init__.py b/otter/generate/__init__.py
index 96029c87..592e9c84 100644
--- a/otter/generate/__init__.py
+++ b/otter/generate/__init__.py
@@ -27,9 +27,10 @@ OTTER_ENV_NAME = "otter-env"
 OTTR_BRANCH = "1.0.0.b0"  # this should match a release tag on GitHub
 
 
-def main(*, tests_path="./tests", output_dir="./", config=None, lang="python", requirements=None, 
-         overwrite_requirements=False, environment=None, no_env=False, username=None, password=None, 
-         token=None, files=[], assignment=None, plugin_collection=None):
+def main(*, tests_path="./tests", output_dir="./", config=None, no_config=False, lang="python", 
+         requirements=None, no_requirements=False, overwrite_requirements=False, environment=None, 
+         no_environment=False, username=None, password=None, token=None, files=[], assignment=None, 
+         plugin_collection=None):
     """
     Runs Otter Generate
 
@@ -37,12 +38,14 @@ def main(*, tests_path="./tests", output_dir="./", config=None, lang="python", r
         tests_path (``str``): path to directory of test files for this assignment
         output_dir (``str``): directory in which to write output zip file
         config (``str``): path to an Otter configuration JSON file
+        no_config (``bool``): disables auto-inclusion of Otter config file at ./otter_config.json
         lang (``str``): the language of the assignment; one of ``["python", "r"]``
         requirements (``str``): path to a Python or R requirements file for this assignment
+        no_requirements (``bool``): disables auto-inclusion of requirements file at ./requirements.txt
         overwrite_requirements (``bool``): whether to overwrite the default requirements instead of
             adding to them
         environment (``str``): path to a conda environment file for this assignment
-        no_env (``bool``): whether ``./evironment.yml`` should be automatically checked if 
+        no_environment (``bool``): whether ``./evironment.yml`` should be automatically checked if 
             ``environment`` is unspecified
         username (``str``): a username for Gradescope for generating a token
         password (``str``): a password for Gradescope for generating a token
@@ -58,7 +61,7 @@ def main(*, tests_path="./tests", output_dir="./", config=None, lang="python", r
             both
     """
     # read in otter_config.json
-    if config is None and os.path.isfile("otter_config.json"):
+    if config is None and os.path.isfile("otter_config.json") and not no_config:
         config = "otter_config.json"
 
     if config is not None and not os.path.isfile(config):
@@ -127,14 +130,18 @@ def main(*, tests_path="./tests", output_dir="./", config=None, lang="python", r
             shutil.copy(file, test_dir)
 
         # open requirements if it exists
-        with load_default_file(requirements, f"requirements.{'R' if options['lang'] == 'r' else 'txt'}") as reqs:
+        with load_default_file(
+            requirements, 
+            f"requirements.{'R' if options['lang'] == 'r' else 'txt'}", 
+            default_disabled=no_requirements
+        ) as reqs:
             template_context["other_requirements"] = reqs if reqs is not None else ""
 
         template_context["overwrite_requirements"] = overwrite_requirements
 
         # open environment if it exists
         # unlike requirements.txt, we will always overwrite, not append by default
-        with load_default_file(environment, "environment.yml", default_disabled=no_env) as env_contents:
+        with load_default_file(environment, "environment.yml", default_disabled=no_environment) as env_contents:
             template_context["other_environment"] = env_contents
             if env_contents is not None:
                 data = yaml.safe_load(env_contents)
diff --git a/otter/generate/templates/python/environment.yml b/otter/generate/templates/python/environment.yml
index 6670efb8..3d7cf42a 100644
--- a/otter/generate/templates/python/environment.yml
+++ b/otter/generate/templates/python/environment.yml
@@ -6,5 +6,7 @@ dependencies:
     - python=3.7
     - pip
     - nb_conda_kernels
+    - gcc_linux-64
+    - gxx_linux-64
     - pip:
         - -r requirements.txt{% else %}{{ other_environment }}{% endif %}
\ No newline at end of file
diff --git a/otter/generate/templates/r/environment.yml b/otter/generate/templates/r/environment.yml
index efdffdb8..dc461583 100644
--- a/otter/generate/templates/r/environment.yml
+++ b/otter/generate/templates/r/environment.yml
@@ -6,6 +6,8 @@ dependencies:
     - python=3.7
     - pip
     - nb_conda_kernels
+    - gcc_linux-64
+    - gxx_linux-64
     - r-base 
     - r-essentials 
     - r-devtools


Test patch:
diff --git a/test/test-assign/gs-autograder-correct/environment.yml b/test/test-assign/gs-autograder-correct/environment.yml
index e1a20111..d1129759 100644
--- a/test/test-assign/gs-autograder-correct/environment.yml
+++ b/test/test-assign/gs-autograder-correct/environment.yml
@@ -6,5 +6,7 @@ dependencies:
     - python=3.7
     - pip
     - nb_conda_kernels
+    - gcc_linux-64
+    - gxx_linux-64
     - pip:
         - -r requirements.txt
\ No newline at end of file
diff --git a/test/test-assign/rmd-autograder-correct/environment.yml b/test/test-assign/rmd-autograder-correct/environment.yml
index da690492..84806c64 100644
--- a/test/test-assign/rmd-autograder-correct/environment.yml
+++ b/test/test-assign/rmd-autograder-correct/environment.yml
@@ -6,6 +6,8 @@ dependencies:
     - python=3.7
     - pip
     - nb_conda_kernels
+    - gcc_linux-64
+    - gxx_linux-64
     - r-base 
     - r-essentials 
     - r-devtools
diff --git a/test/test-run/autograder/source/environment.yml b/test/test-run/autograder/source/environment.yml
index e1a20111..d1129759 100644
--- a/test/test-run/autograder/source/environment.yml
+++ b/test/test-run/autograder/source/environment.yml
@@ -6,5 +6,7 @@ dependencies:
     - python=3.7
     - pip
     - nb_conda_kernels
+    - gcc_linux-64
+    - gxx_linux-64
     - pip:
         - -r requirements.txt
\ No newline at end of file
diff --git a/test/test_generate/test-autograder/autograder-correct/environment.yml b/test/test_generate/test-autograder/autograder-correct/environment.yml
index e1a20111..d1129759 100644
--- a/test/test_generate/test-autograder/autograder-correct/environment.yml
+++ b/test/test_generate/test-autograder/autograder-correct/environment.yml
@@ -6,5 +6,7 @@ dependencies:
     - python=3.7
     - pip
     - nb_conda_kernels
+    - gcc_linux-64
+    - gxx_linux-64
     - pip:
         - -r requirements.txt
\ No newline at end of file
diff --git a/test/test_generate/test-autograder/autograder-token-correct/environment.yml b/test/test_generate/test-autograder/autograder-token-correct/environment.yml
index e1a20111..d1129759 100644
--- a/test/test_generate/test-autograder/autograder-token-correct/environment.yml
+++ b/test/test_generate/test-autograder/autograder-token-correct/environment.yml
@@ -6,5 +6,7 @@ dependencies:
     - python=3.7
     - pip
     - nb_conda_kernels
+    - gcc_linux-64
+    - gxx_linux-64
     - pip:
         - -r requirements.txt
\ No newline at end of file
diff --git a/test/test_generate/test_autograder.py b/test/test_generate/test_autograder.py
index b37b2575..62e12f8f 100644
--- a/test/test_generate/test_autograder.py
+++ b/test/test_generate/test_autograder.py
@@ -32,7 +32,7 @@ class TestAutograder(TestCase):
             output_dir = TEST_FILES_PATH,
             requirements = TEST_FILES_PATH + "requirements.txt",
             files = [TEST_FILES_PATH + "data/test-df.csv"],
-            no_env = True,  # don't use the environment.yml in the root of the repo
+            no_environment = True,  # don't use the environment.yml in the root of the repo
         )
 
         with self.unzip_to_temp(TEST_FILES_PATH + "autograder.zip", delete=True) as unzipped_dir:
@@ -50,7 +50,7 @@ class TestAutograder(TestCase):
                 requirements = TEST_FILES_PATH + "requirements.txt",
                 config = TEST_FILES_PATH + "otter_config.json",
                 files = [TEST_FILES_PATH + "data/test-df.csv"],
-                no_env = True,  # don't use the environment.yml in the root of the repo
+                no_environment = True,  # don't use the environment.yml in the root of the repo
             )
             mocked_client.assert_not_called()
     
diff --git a/test/test_grade.py b/test/test_grade.py
index 9014fe9c..20e29996 100644
--- a/test/test_grade.py
+++ b/test/test_grade.py
@@ -63,7 +63,7 @@ class TestGrade(TestCase):
             requirements = TEST_FILES_PATH + "requirements.txt", 
             output_dir = TEST_FILES_PATH,
             config = TEST_FILES_PATH + "otter_config.json" if pdfs else None,
-            no_env = True,
+            no_environment = True,
         )
 
     def test_docker(self):