def wrapper(self): if not which("valgrind"): raise Error("valgrind not installed", result=Checks.SKIP) self._valgrind = True try: func(self) self._check_valgrind() finally: self._valgrind = False
def run(): global args global exes global prefix r = 1 optional_prefix = False for arg in args: if arg.startswith('---p='): arg = arg[len('---p='):] prefix += arg.split(',') break if arg.startswith('---op='): arg = arg[len('---op='):] prefix += arg.split(',') break if prefix and not optional_prefix and not which(prefix[0]): print >> sys.stderr, "prefix not found" return 1 if "---l" in args: print os.path.join(cwd,exes[0]) return 0 elif "---d" in args: print os.path.dirname(cwd) return 0 else: args = [a for a in args if not a.startswith("---")] try: with open(os.path.join(cwd, os.path.dirname(exes[0]), "sg.json"), 'r') as f: j = json.load(f) args += j['args'] except: pass if os.name=="nt": r = subprocess.call(prefix + [os.path.join(cwd,exes[0])] + args, cwd=(cwd)) else: r = subprocess.call(prefix + ['./'+exes[0]] + args, cwd=(cwd)) return r
def submit(org, branch): """Submit work.""" # check announcements res = requests.get("https://cs50.me/status/submit50") if res.status_code == 200 and res.text.strip(): raise Error(res.text.strip()) # require git 2.7+, so that credential-cache--daemon ignores SIGHUP # https://github.com/git/git/blob/v2.7.0/credential-cache--daemon.c if not which("git"): raise Error( _("You don't have git. Install git, then re-run {}!".format(org))) version = subprocess.check_output(["git", "--version"]).decode("utf-8") matches = re.search(r"^git version (\d+\.\d+\.\d+).*$", version) if not matches or StrictVersion(matches.group(1)) < StrictVersion("2.7.0"): raise Error( _("You have an old version of git. Install version 2.7 or later, then re-run {}!" .format(org))) # update progress progress("Connecting") # compute timestamp global timestamp headers = requests.get("https://api.github.com/").headers timestamp = datetime.datetime.strptime(headers["Date"], "%a, %d %b %Y %H:%M:%S %Z") timestamp = timestamp.strftime("%Y%m%dT%H%M%SZ") # check version res = requests.get("https://cs50.me/versions/submit50") if res.status_code != 200: raise Error( _("You have an unknown version of submit50. " "Email [email protected]!")) version_required = res.text.strip() if parse_version(version_required) > parse_version( get_distribution("submit50").version): raise Error( _("You have an old version of submit50. " "Run update50, then re-run {}!".format(org))) # separate branch into slug and repo check_repo = "@cs50/checks" branch = branch if not branch.endswith( check_repo) else branch[:-len(check_repo)] try: slug, src = branch.split("@") except ValueError: slug, src = branch, "cs50/checks" # ensure slug exists file, submit.EXCLUDE = tempfile.mkstemp() url = "https://github.com/{}/raw/master/{}/submit50/exclude".format( src, slug) try: urllib.request.urlretrieve(url, filename=submit.EXCLUDE) lines = open(submit.EXCLUDE) except Exception as e: if run.verbose: cprint(str(e)) e = Error(_("Invalid slug. Did you mean to submit something else?")) e.__cause__ = None raise e if slug.startswith("/") and slug.endswith("/"): raise Error( _("Invalid slug. Did you mean {}, without the leading and trailing slashes?" .format(slug.strip("/")))) elif slug.startswith("/"): raise Error( _("Invalid slug. Did you mean {}, without the leading slash?". format(slug.strip("/")))) elif slug.endswith("/"): raise Error( _("Invalid slug. Did you mean {}, without the trailing slash?". format(slug.strip("/")))) # check for missing files missing = [] for line in lines: matches = re.match(r"^\s*#\s*([^\s]+)\s*$", line) if matches: pattern = matches.group(1) if pattern[:-1] == "/": if not os.path.isdir(pattern): missing.append(pattern) elif not os.path.isfile(pattern): missing.append(pattern) if missing: cprint(_("You seem to be missing these files:")) for pattern in missing: cprint(" {}".format(pattern)) raise Error(_("Ensure you have the required files before submitting.")) # update progress progress(_("Authenticating")) # authenticate user via SSH try: # require ssh assert which("ssh") # require GitHub username in ~/.gitconfig username, password = run( "git config --global credential.https://github.com/submit50.username", quiet=True), None email = "{}@users.noreply.github.com".format(username) repo = "[email protected]:{}/{}.git".format(org, username) progress(False) # require ssh-agent child = pexpect.spawn("ssh [email protected]") i = child.expect([ "Enter passphrase for key", "Are you sure you want to continue connecting", pexpect.EOF ]) child.close() assert i == 2 # authenticate user via HTTPS except BaseException: username, password, email = authenticate(org) repo = "https://{}@github.com/{}/{}".format(username, org, username) # update progress progress(_("Preparing")) # clone repository try: run("git clone --bare {} {}".format(shlex.quote(repo), shlex.quote(run.GIT_DIR)), password=password) except BaseException: if password: e = Error( _("Looks like {} isn't enabled for your account yet. " "Go to https://cs50.me/authorize and make sure you accept any pending invitations!" .format(org, org))) else: e = Error( _("Looks like you have the wrong username in ~/.gitconfig or {} isn't yet enabled for your account. " "Double-check ~/.gitconfig and then log into https://cs50.me/ in a browser, " "click \"Authorize application\" if prompted, and re-run {} here." .format(org, org))) e.__cause__ = None raise e # check out .gitattributes, if any, temporarily shadowing student's, if any if os.path.isfile(".gitattributes"): submit.ATTRIBUTES = ".gitattributes.{}".format(round(time.time())) os.rename(".gitattributes", submit.ATTRIBUTES) try: run("git checkout --force {} .gitattributes".format(branch)) except Exception: pass # set options tag = "{}@{}".format(branch, timestamp) run("git config user.email {}".format(shlex.quote(email))) run("git config user.name {}".format(shlex.quote(username))) run("git symbolic-ref HEAD refs/heads/{}".format(shlex.quote(branch))) # patterns of file names to exclude run("git config core.excludesFile {}".format(shlex.quote(submit.EXCLUDE))) # blocklist for git-lfs # https://github.com/git-lfs/git-lfs/blob/master/commands/command_track.go with open("{}/info/exclude".format(run.GIT_DIR), "w") as file: file.write(".git*\n") file.write(".lfs*\n") # adds, modifies, and removes index entries to match the working tree run("git add --all") # get file lists files = run("git ls-files").splitlines() others = run("git ls-files --exclude-from={}/info/exclude --other".format( run.GIT_DIR)).splitlines() # unescape any octal codes in lists # https://stackoverflow.com/a/46650050/5156190 def unescape(s): if s.startswith('"') and s.endswith('"'): return (s.replace('"', '').encode("latin1").decode( "unicode-escape").encode("latin1").decode("utf8")) return s files = [unescape(file) for file in files] others = [unescape(other) for other in others] # hide .gitattributes, if any, from output if ".gitattributes" in files: files.remove(".gitattributes") # check for large files > 100 MB (and huge files > 2 GB) # https://help.github.com/articles/conditions-for-large-files/ # https://help.github.com/articles/about-git-large-file-storage/ larges, huges = [], [] for file in files: size = os.path.getsize(file) if size > (100 * 1024 * 1024): larges.append(file) elif size > (2 * 1024 * 1024 * 1024): huges.append(file) if len(huges) > 0: raise Error( _("These files are too large to be submitted:\n{}\n" "Remove these files from your directory " "and then re-run {}!").format("\n".join(huges), org)) elif len(larges) > 0: if not which("git-lfs"): raise Error( _("These files are too large to be submitted:\n{}\n" "Install git-lfs (or remove these files from your directory) " "and then re-run {}!").format("\n".join(larges), org)) run("git lfs install --local") run("git config credential.helper cache") # for pre-push hook for large in larges: run("git rm --cached {}".format(large)) run("git lfs track {}".format(large)) run("git add {}".format(large)) run("git add --force .gitattributes") # files that will be submitted if len(files) == 0: raise Error( _("No files in this directory are expected for submission.")) # prompts for submit50 if org == "submit50": if files: cprint(_("Files that will be submitted:"), "green") for file in files: cprint("./{}".format(file), "green") # files that won't be submitted if others: cprint(_("Files that won't be submitted:"), "yellow") for other in others: cprint("./{}".format(other), "yellow") # prompt for honesty readline.clear_history() try: answer = input( _("Keeping in mind the course's policy on academic honesty, " "are you sure you want to submit these files (yes/no)? ")) except EOFError: answer = None print() if not answer or not re.match("^\s*(?:y|yes)\s*$", answer, re.I): raise Error(_("No files were submitted.")) # update progress if org == "submit50": progress(_("Submitting")) else: progress(_("Uploading")) # push branch run("git commit --allow-empty --message='{}'".format(timestamp)) commit_hash = run("git rev-parse HEAD") run("git push origin 'refs/heads/{}'".format(branch), password=password) # successful submission if org == "submit50": cprint( _("Submitted {}! See https://cs50.me/submissions.").format(branch), "green") progress(False) return username, commit_hash
from backports.shutil_which import which else: import subprocess from shutil import which try: import threading except ImportError: import dummy_threading as threading try: import psutil TIMEOUT = 1 except ImportError: psutil = None PS_PATH = which('ps') PGREP_PATH = which('pgrep') TIMEOUT = None if PS_PATH is None else 1 __all__ = ["openpty", "fork", "spawn"] STDIN_FILENO = 0 STDOUT_FILENO = 1 STDERR_FILENO = 2 CHILD = 0 def openpty(): """openpty() -> (master_fd, slave_fd) Open a pty master/slave pair, using os.openpty() if possible."""
from os.path import join, dirname from subprocess import check_call import json import re import shutil import tempfile try: which = shutil.which except: from backports.shutil_which import which NPM = which("npm") HERE = dirname(__file__) TESTS = join(HERE, "src", "tests") # TODO: hoist these to the recipe PKG = { "devDependencies": { "jsdoc": "3.6.3", "typedoc": "0.15.0" }, "scripts": { "test": "python -m pytest -vv" } } def test_in_tmp(tmp): print( "- creating package.json to ensure js/tsdoc are installed/on path...")
def submit(org, problem): """Submit problem.""" # require git 2.7+, so that credential-cache--daemon ignores SIGHUP # https://github.com/git/git/blob/v2.7.0/credential-cache--daemon.c if not which("git"): raise Error("You don't have git. Install git, then re-run submit50!.") version = subprocess.check_output(["git", "--version"]).decode("utf-8") matches = re.search(r"^git version (\d+\.\d+\.\d+).*$", version) if not matches or StrictVersion(matches.group(1)) < StrictVersion("2.7.0"): raise Error("You have an old version of git. Install version 2.7 or later, then re-run submit50!") # update spinner spin("Connecting") # compute timestamp global timestamp headers = requests.get("https://api.github.com/").headers timestamp = datetime.datetime.strptime(headers["Date"], "%a, %d %b %Y %H:%M:%S %Z") timestamp = timestamp.strftime("%Y%m%dT%H%M%SZ") # check version res = requests.get("https://cs50.me/versions/submit50") if res.status_code != 200: raise Error("You have an unknown version of submit50. " + "Email [email protected]!") version_required = res.text.strip() if parse_version(version_required) > parse_version(get_distribution("submit50").version): raise Error("You have an old version of submit50. " + "Run update50, then re-run submit50!") # assume cs50/ problem if problem name begins with a year branch = problem if problem.split("/")[0].isdigit(): branch = os.path.join("cs50", problem) # ensure problem exists _, submit.EXCLUDE = tempfile.mkstemp() url = "https://cs50.me/excludes/{}/".format(branch) try: urllib.request.urlretrieve(url, filename=submit.EXCLUDE) lines = open(submit.EXCLUDE) except Exception as e: if run.verbose: cprint(str(e)) e = Error("Invalid problem. Did you mean to submit something else?") e.__cause__ = None raise e # check for missing files missing = [] for line in lines: matches = re.match(r"^\s*#\s*([^\s]+)\s*$", line) if matches: pattern = matches.group(1) if pattern[:-1] == "/": if not os.path.isdir(pattern): missing.append(pattern) elif not os.path.isfile(pattern): missing.append(pattern) if missing: cprint("You seem to be missing these files:") for pattern in missing: cprint(" {}".format(pattern)) raise Error("Ensure you have the required files before submitting.") # update spinner spin("Authenticating") # authenticate user via SSH try: assert which("ssh") username, password = run("git config --global credential.https://github.com/submit50.username", quiet=True), None email = "{}@users.noreply.github.com".format(username) repo = "[email protected]:{}/{}.git".format(org, username) with open(os.devnull, "w") as DEVNULL: spin(False) assert subprocess.call(["ssh", "*****@*****.**"], stderr=DEVNULL) == 1 # successfully authenticated # authenticate user via HTTPS except: username, password, email = authenticate(org) repo = "https://{}@github.com/{}/{}".format(username, org, username) # update spinner spin("Preparing") # clone repository try: run("git clone --bare {} {}".format(shlex.quote(repo), shlex.quote(run.GIT_DIR)), password=password) except: if password: e = Error("Looks like submit50 isn't enabled for your account yet. " + "Log into https://cs50.me/ in a browser, click \"Authorize application\", and re-run submit50 here!") else: e = Error("Looks like you have the wrong username in ~/.gitconfig or submit50 isn't yet enabled for your account. " + "Double-check ~/.gitconfig and then log into https://cs50.me/ in a browser, " + "click \"Authorize application\" if prompted, and re-run submit50 here.") e.__cause__ = None raise e # set options tag = "{}@{}".format(branch, timestamp) run("git config user.email {}".format(shlex.quote(email))) run("git config user.name {}".format(shlex.quote(username))) run("git symbolic-ref HEAD refs/heads/{}".format(shlex.quote(branch))) # patterns of file names to exclude run("git config core.excludesFile {}".format(shlex.quote(submit.EXCLUDE))) # adds, modifies, and removes index entries to match the working tree run("git add --all") # get file lists files = run("git ls-files").split() other = run("git ls-files --other").split() # files that will be submitted if len(files) == 0: raise Error("No files in this directory are expected for submission.") cprint("Files that will be submitted:", "green") for f in files: cprint("./{}".format(f), "green") # files that won't be submitted if len(other) != 0: cprint("Files that won't be submitted:", "yellow") for f in other: cprint("./{}".format(f), "yellow") # prompt for academic honesty cprint("Keeping in mind the course's policy on academic honesty, " + "are you sure you want to submit these files?", end=" ") if not re.match("^\s*(?:y|yes)\s*$", input(), re.I): raise Error("No files were submitted.") # restart spinner spin("Submitting") # push branch run("git commit --allow-empty --message='{}'".format(timestamp)) run("git push origin 'refs/heads/{}'".format(branch), password=password) # successful submission cprint("Submitted {}! ".format(problem) + "See https://cs50.me/submissions/{}.".format(branch), "green")