1
+ #!/usr/bin/env python
2
+ # Copyright (c) Jupyter Development Team.
3
+ # Distributed under the terms of the Modified BSD License.
4
+
5
+ # The run-hooks.sh script looks for *.sh scripts to source
6
+ # and executable files to run within a passed directory
7
+ import os
8
+ from textwrap import dedent
9
+ import json
10
+ import tempfile
11
+ import sys
12
+ import subprocess
13
+ from pathlib import PosixPath
14
+
15
+
16
+ def source (path : PosixPath ):
17
+ """
18
+ Emulate the bash `source` command accurately
19
+
20
+ When used in bash, `source` executes the passed file in the current 'context'
21
+ of the script from where it is called. This primarily deals with how
22
+ bash (and thus environment variables) are modified.
23
+
24
+ 1. Any bash variables (particularly any set via `export`) are passed on to the
25
+ sourced script as their values are at the point source is called
26
+ 2. The sourced script can itself use `export` to affect the bash variables of the
27
+ parent script that called it.
28
+
29
+ (2) is the primary difference between `source` and just calling a shell script,
30
+ and makes it possible for a set of scripts running in sequence to share data by
31
+ passing bash variables across with `export`.
32
+
33
+ Given bash variables are environment variables, we will simply look for all modified
34
+ environment variables in the script we have sourced, and update the calling python
35
+ script's environment variables to match.
36
+
37
+ Args:
38
+ path (PosixPath): Valid bash script to source
39
+ """
40
+ # We start a bash process and have it `source` the script we are given. Then, we
41
+ # use python (for convenience) to dump the environment variables from the bash process into
42
+ # json (we could use `env` but then handling multiline variable values becomes a nightmare).
43
+ # The json is written to a temporary file we create. We read this json, and update our python
44
+ # process' environment variable with whatever we get back from bash.
45
+ with tempfile .NamedTemporaryFile () as bash_file , tempfile .NamedTemporaryFile () as py_file , tempfile .NamedTemporaryFile () as env_vars_file :
46
+ py_file .write (
47
+ dedent (
48
+ f"""
49
+ import os
50
+ import json
51
+ with(open("{ env_vars_file .name } ", "w")) as f:
52
+ json.dump(dict(os.environ), f)
53
+ """
54
+ ).encode ()
55
+ )
56
+ py_file .flush ()
57
+
58
+ bash_file .write (
59
+ dedent (
60
+ f"""
61
+ #!/bin/bash
62
+ source { path }
63
+ python { py_file .name }
64
+ """
65
+ ).encode ()
66
+ )
67
+ bash_file .flush ()
68
+
69
+ run = subprocess .run (["/bin/bash" , bash_file .name ])
70
+
71
+ if run .returncode != 0 :
72
+ print (
73
+ f"{ path } has failed with return code { run .returncode } , continuing execution"
74
+ )
75
+ return
76
+
77
+ env_vars = json .load (env_vars_file )
78
+ os .environ .update (env_vars )
79
+
80
+
81
+ if len (sys .argv ) != 2 :
82
+ print ("Should pass exactly one directory" )
83
+ sys .exit (1 )
84
+
85
+ hooks_directory = PosixPath (sys .argv [1 ])
86
+
87
+ if not hooks_directory .exists ():
88
+ print (f"Directory { hooks_directory } does not exist" )
89
+
90
+ if not hooks_directory .is_dir ():
91
+ print (f"{ hooks_directory } is not a directory" )
92
+
93
+ print (f"Running hooks in: { hooks_directory } as { os .getuid ()} gid: { os .getgid ()} " )
94
+
95
+ for f in hooks_directory .iterdir ():
96
+ if f .suffix == ".sh" :
97
+ print (f"Sourcing shell script: { f } " )
98
+ source (f )
99
+ elif os .access (f , os .X_OK ):
100
+ print (f"Running executable: { f } " )
101
+ run = subprocess .run ([str (f )])
102
+ if run .returncode != 0 :
103
+ print (
104
+ f"{ f } has failed with return code { run .returncode } , continuing execution"
105
+ )
106
+ else :
107
+ print (f"Ignoring non-executable file { f } " )
108
+
109
+
110
+ print (f"Done running hooks in: { hooks_directory } " )
111
+
112
+ print (os .environ ['HELLO' ])
0 commit comments