Add [Hook Scripts] config section.
BUG=chromium-os:34021
TEST=CL:30805
Change-Id: I21855971282da8ac21eb0f981bfdd9ef800e7b49
Reviewed-on: https://gerrit.chromium.org/gerrit/30806
Tested-by: Jon Salz <jsalz@chromium.org>
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Ready: Jon Salz <jsalz@chromium.org>
diff --git a/pre-upload.py b/pre-upload.py
index 467dc04..7be4ea7 100755
--- a/pre-upload.py
+++ b/pre-upload.py
@@ -4,6 +4,7 @@
# found in the LICENSE file.
import ConfigParser
+import functools
import json
import optparse
import os
@@ -378,6 +379,35 @@
return HookFailure(msg)
+def _run_project_hook_script(script, project, commit):
+ """Runs a project hook script.
+
+ The script is run with the following environment variables set:
+ PRESUBMIT_PROJECT: The affected project
+ PRESUBMIT_COMMIT: The affected commit
+ PRESUBMIT_FILES: A newline-separated list of affected files
+
+ The script is considered to fail if the exit code is non-zero. It should
+ write an error message to stdout.
+ """
+ env = dict(os.environ)
+ env['PRESUBMIT_PROJECT'] = project
+ env['PRESUBMIT_COMMIT'] = commit
+
+ # Put affected files in an environment variable
+ files = _get_affected_files(commit)
+ env['PRESUBMIT_FILES'] = '\n'.join(files)
+
+ process = subprocess.Popen(script, env=env, shell=True,
+ stdin=open(os.devnull),
+ stdout=subprocess.PIPE)
+ stdout, _ = process.communicate()
+ if process.wait():
+ return HookFailure('Hook script %s failed with code %d%s' %
+ (script, process.returncode,
+ ':\n' + stdout if stdout else ''))
+
+
# Base
@@ -424,21 +454,20 @@
}
-def _get_disabled_hooks():
+def _get_disabled_hooks(config):
"""Returns a set of hooks disabled by the current project's config file.
Expects to be called within the project root.
+
+ Args:
+ config: A ConfigParser for the project's config file.
"""
SECTION = 'Hook Overrides'
- config = ConfigParser.RawConfigParser()
- try:
- config.read(_CONFIG_FILE)
- flags = config.options(SECTION)
- except ConfigParser.Error:
- return set([])
+ if not config.has_section(SECTION):
+ return set()
disable_flags = []
- for flag in flags:
+ for flag in config.options(SECTION):
try:
if not config.getboolean(SECTION, flag): disable_flags.append(flag)
except ValueError as e:
@@ -449,18 +478,43 @@
return set([_DISABLE_FLAGS[key] for key in disabled_keys])
+def _get_project_hook_scripts(config):
+ """Returns a list of project-specific hook scripts.
+
+ Args:
+ config: A ConfigParser for the project's config file.
+ """
+ SECTION = 'Hook Scripts'
+ if not config.has_section(SECTION):
+ return []
+
+ hook_names_values = config.items(SECTION)
+ hook_names_values.sort(key=lambda x: x[0])
+ return [x[1] for x in hook_names_values]
+
+
def _get_project_hooks(project):
"""Returns a list of hooks that need to be run for a project.
Expects to be called from within the project root.
"""
- disabled_hooks = _get_disabled_hooks()
+ config = ConfigParser.RawConfigParser()
+ try:
+ config.read(_CONFIG_FILE)
+ except ConfigParser.Error:
+ # Just use an empty config file
+ config = ConfigParser.RawConfigParser()
+
+ disabled_hooks = _get_disabled_hooks(config)
hooks = [hook for hook in _COMMON_HOOKS if hook not in disabled_hooks]
if project in _PROJECT_SPECIFIC_HOOKS:
hooks.extend(hook for hook in _PROJECT_SPECIFIC_HOOKS[project]
if hook not in disabled_hooks)
+ for script in _get_project_hook_scripts(config):
+ hooks.append(functools.partial(_run_project_hook_script, script))
+
return hooks