22
33set -euo pipefail
44
5- # Ensure the script is being executed in the cccl / root
5+ # Ensure the script is being executed in the nvbench / root
66cd " $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd ) /.." ;
77
88print_help () {
@@ -11,17 +11,46 @@ print_help() {
1111 echo " the top-level devcontainer in .devcontainer/devcontainer.json will be used."
1212 echo " "
1313 echo " Options:"
14- echo " -c, --cuda Specify the CUDA version. E.g., 12.2"
15- echo " -H, --host Specify the host compiler. E.g., gcc12"
16- echo " -d, --docker Launch the development environment in Docker directly without using VSCode."
17- echo " -h, --help Display this help message and exit."
14+ echo " -c, --cuda Specify the CUDA version. E.g., 12.2"
15+ echo " -H, --host Specify the host compiler. E.g., gcc12"
16+ echo " -d, --docker Launch the development environment in Docker directly without using VSCode."
17+ echo " --gpus gpu-request GPU devices to add to the container ('all' to pass all GPUs)."
18+ echo " -e, --env list Set additional container environment variables."
19+ echo " -v, --volume list Bind mount a volume."
20+ echo " -h, --help Display this help message and exit."
21+ }
22+
23+ # Assign variable one scope above the caller
24+ # Usage: local "$1" && _upvar $1 "value(s)"
25+ # Param: $1 Variable name to assign value to
26+ # Param: $* Value(s) to assign. If multiple values, an array is
27+ # assigned, otherwise a single value is assigned.
28+ # See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
29+ _upvar () {
30+ if unset -v " $1 " ; then
31+ if (( $# == 2 )) ; then
32+ eval $1 =\"\$ 2\" ;
33+ else
34+ eval $1 =\(\"\$ {@:2}\"\) ;
35+ fi ;
36+ fi
1837}
1938
2039parse_options () {
21- local OPTIONS=c:H:dh
22- local LONG_OPTIONS=cuda:,host:,docker,help
40+ local -;
41+ set -euo pipefail;
42+
43+ # Read the name of the variable in which to return unparsed arguments
44+ local UNPARSED=" ${!# } " ;
45+ # Splice the unparsed arguments variable name from the arguments list
46+ set -- " ${@: 1: $# -1} " ;
47+
48+ local OPTIONS=c:e:H:dhv
49+ local LONG_OPTIONS=cuda:,env:,host:,gpus:,volume:,docker,help
50+ # shellcheck disable=SC2155
2351 local PARSED_OPTIONS=$( getopt -n " $0 " -o " ${OPTIONS} " --long " ${LONG_OPTIONS} " -- " $@ " )
2452
53+ # shellcheck disable=SC2181
2554 if [[ $? -ne 0 ]]; then
2655 exit 1
2756 fi
@@ -34,10 +63,18 @@ parse_options() {
3463 cuda_version=" $2 "
3564 shift 2
3665 ;;
66+ -e|--env)
67+ env_vars+=(" $1 " " $2 " )
68+ shift 2
69+ ;;
3770 -H|--host)
3871 host_compiler=" $2 "
3972 shift 2
4073 ;;
74+ --gpus)
75+ gpu_request=" $2 "
76+ shift 2
77+ ;;
4178 -d|--docker)
4279 docker_mode=true
4380 shift
@@ -46,8 +83,13 @@ parse_options() {
4683 print_help
4784 exit 0
4885 ;;
86+ -v|--volume)
87+ volumes+=(" $1 " " $2 " )
88+ shift 2
89+ ;;
4990 --)
5091 shift
92+ _upvar " ${UNPARSED} " " ${@ } "
5193 break
5294 ;;
5395 * )
@@ -59,20 +101,153 @@ parse_options() {
59101 done
60102}
61103
104+ # shellcheck disable=SC2155
62105launch_docker () {
63- DOCKER_IMAGE=$( grep " image" " ${path} /devcontainer.json" | sed ' s/.*: "\(.*\)",/\1/' )
64- echo " Found image: ${DOCKER_IMAGE} "
65- docker pull ${DOCKER_IMAGE}
66- docker run \
67- -it --rm \
68- --user coder \
69- --workdir /home/coder/cccl \
70- --mount type=bind,src=" $( pwd) " ,dst=' /home/coder/cccl' \
71- ${DOCKER_IMAGE} \
72- /bin/bash
106+ local -;
107+ set -euo pipefail
108+
109+ inline_vars () {
110+ cat - \
111+ ` # inline local workspace folder` \
112+ | sed " s@\$ {localWorkspaceFolder}@$( pwd) @g" \
113+ ` # inline local workspace folder basename` \
114+ | sed " s@\$ {localWorkspaceFolderBasename}@$( basename " $( pwd) " ) @g" \
115+ ` # inline container workspace folder` \
116+ | sed " s@\$ {containerWorkspaceFolder}@${WORKSPACE_FOLDER:- } @g" \
117+ ` # inline container workspace folder basename` \
118+ | sed " s@\$ {containerWorkspaceFolderBasename}@$( basename " ${WORKSPACE_FOLDER:- } " ) @g" \
119+ ` # translate local envvars to shell syntax` \
120+ | sed -r ' s/\$\{localEnv:([^\:]*):?(.*)\}/${\1:-\2}/g'
121+ }
122+
123+ args_to_path () {
124+ local -a keys=(" ${@ } " )
125+ keys=(" ${keys[@]/#/ [} " )
126+ keys=(" ${keys[@]/%/ ]} " )
127+ echo " $( IFS=; echo " ${keys[*]} " ) "
128+ }
129+
130+ json_string () {
131+ python3 -c " import json,sys; print(json.load(sys.stdin)$( args_to_path " ${@ } " ) )" 2> /dev/null | inline_vars
132+ }
133+
134+ json_array () {
135+ python3 -c " import json,sys; [print(f'\" {x}\" ') for x in json.load(sys.stdin)$( args_to_path " ${@ } " ) ]" 2> /dev/null | inline_vars
136+ }
137+
138+ json_map () {
139+ python3 -c " import json,sys; [print(f'{k}=\" {v}\" ') for k,v in json.load(sys.stdin)$( args_to_path " ${@ } " ) .items()]" 2> /dev/null | inline_vars
140+ }
141+
142+ devcontainer_metadata_json () {
143+ docker inspect --type image --format ' {{json .Config.Labels}}' " $DOCKER_IMAGE " \
144+ | json_string ' "devcontainer.metadata"'
145+ }
146+
147+ # ##
148+ # Read relevant values from devcontainer.json
149+ # ##
150+
151+ local devcontainer_json=" ${path} /devcontainer.json" ;
152+
153+ # Read image
154+ local DOCKER_IMAGE=" $( json_string ' "image"' < " ${devcontainer_json} " ) "
155+ # Always pull the latest copy of the image
156+ docker pull " $DOCKER_IMAGE "
157+
158+ # Read workspaceFolder
159+ local WORKSPACE_FOLDER=" $( json_string ' "workspaceFolder"' < " ${devcontainer_json} " ) "
160+ # Read remoteUser
161+ local REMOTE_USER=" $( json_string ' "remoteUser"' < " ${devcontainer_json} " ) "
162+ # If remoteUser isn't in our devcontainer.json, read it from the image's "devcontainer.metadata" label
163+ if test -z " ${REMOTE_USER:- } " ; then
164+ REMOTE_USER=" $( devcontainer_metadata_json | json_string " -1" ' "remoteUser"' ) "
165+ fi
166+ # Read runArgs
167+ local -a RUN_ARGS=" ($( json_array ' "runArgs"' < " ${devcontainer_json} " ) )"
168+ # Read initializeCommand
169+ local -a INITIALIZE_COMMAND=" ($( json_array ' "initializeCommand"' < " ${devcontainer_json} " ) )"
170+ # Read containerEnv
171+ local -a ENV_VARS=" ($( json_map ' "containerEnv"' < " ${devcontainer_json} " | sed -r ' s/(.*)=(.*)/--env \1=\2/' ) )"
172+ # Read mounts
173+ local -a MOUNTS=" ($(
174+ tee < " ${devcontainer_json} " \
175+ 1> /dev/null \
176+ >( json_array ' "mounts"' ) \
177+ >( json_string ' "workspaceMount"' ) \
178+ | xargs -r -I% echo --mount ' %'
179+ ) )"
180+
181+ # ##
182+ # Update run arguments and container environment variables
183+ # ##
184+
185+ # Only pass `-it` if the shell is a tty
186+ if ! ${CI:- ' false' } && tty > /dev/null 2>&1 && (exec < /dev/tty); then
187+ RUN_ARGS+=(" -it" )
188+ fi
189+
190+ for flag in rm init; do
191+ if [[ " ${RUN_ARGS[*]} " != * " --${flag} " * ]]; then
192+ RUN_ARGS+=(" --${flag} " )
193+ fi
194+ done
195+
196+ # Prefer the user-provided --gpus argument
197+ if test -n " ${gpu_request:- } " ; then
198+ RUN_ARGS+=(--gpus " ${gpu_request} " )
199+ else
200+ # Otherwise read and infer from hostRequirements.gpu
201+ local GPU_REQUEST=" $( json_string ' "hostRequirements"' ' "gpu"' < " ${devcontainer_json} " ) "
202+ if test " ${GPU_REQUEST:- false} " = true ; then
203+ RUN_ARGS+=(--gpus all)
204+ elif test " ${GPU_REQUEST:- false} " = optional && \
205+ command -v nvidia-container-runtime > /dev/null 2>&1 ; then
206+ RUN_ARGS+=(--gpus all)
207+ fi
208+ fi
209+
210+ RUN_ARGS+=(--workdir " ${WORKSPACE_FOLDER:-/ home/ coder/ nvbench} " )
211+
212+ if test -n " ${REMOTE_USER:- } " ; then
213+ ENV_VARS+=(--env NEW_UID=" $( id -u) " )
214+ ENV_VARS+=(--env NEW_GID=" $( id -g) " )
215+ ENV_VARS+=(--env REMOTE_USER=" $REMOTE_USER " )
216+ RUN_ARGS+=(-u root:root)
217+ RUN_ARGS+=(--entrypoint " ${WORKSPACE_FOLDER:-/ home/ coder/ nvbench} /.devcontainer/docker-entrypoint.sh" )
218+ fi
219+
220+ if test -n " ${SSH_AUTH_SOCK:- } " ; then
221+ ENV_VARS+=(--env " SSH_AUTH_SOCK=/tmp/ssh-auth-sock" )
222+ MOUNTS+=(--mount " source=${SSH_AUTH_SOCK} ,target=/tmp/ssh-auth-sock,type=bind" )
223+ fi
224+
225+ # Append user-provided volumes
226+ if test -v volumes && test ${# volumes[@]} -gt 0; then
227+ MOUNTS+=(" ${volumes[@]} " )
228+ fi
229+
230+ # Append user-provided envvars
231+ if test -v env_vars && test ${# env_vars[@]} -gt 0; then
232+ ENV_VARS+=(" ${env_vars[@]} " )
233+ fi
234+
235+ # Run the initialize command before starting the container
236+ if test " ${# INITIALIZE_COMMAND[@]} " -gt 0; then
237+ eval " ${INITIALIZE_COMMAND[*]@ Q} "
238+ fi
239+
240+ exec docker run \
241+ " ${RUN_ARGS[@]} " \
242+ " ${ENV_VARS[@]} " \
243+ " ${MOUNTS[@]} " \
244+ " ${DOCKER_IMAGE} " \
245+ " $@ "
73246}
74247
75248launch_vscode () {
249+ local -;
250+ set -euo pipefail;
76251 # Since Visual Studio Code allows only one instance per `devcontainer.json`,
77252 # this code prepares a unique temporary directory structure for each launch of a devcontainer.
78253 # By doing so, it ensures that multiple instances of the same environment can be run
@@ -85,10 +260,10 @@ launch_vscode() {
85260 mkdir -p " ${tmpdir} "
86261 mkdir -p " ${tmpdir} /.devcontainer"
87262 cp -arL " ${path} /devcontainer.json" " ${tmpdir} /.devcontainer"
88- sed -i ' s@\\${localWorkspaceFolder}@$(pwd)@g' " ${tmpdir} /.devcontainer/devcontainer.json"
263+ sed -i " s@\\ ${localWorkspaceFolder} @$( pwd) @g" " ${tmpdir} /.devcontainer/devcontainer.json"
89264 local path=" ${tmpdir} "
90265 local hash=" $( echo -n " ${path} " | xxd -pu - | tr -d ' [:space:]' ) "
91- local url=" vscode://vscode-remote/dev-container+${hash} /home/coder/cccl "
266+ local url=" vscode://vscode-remote/dev-container+${hash} /home/coder/nvbench "
92267
93268 local launch=" "
94269 if type open > /dev/null 2>&1 ; then
@@ -105,7 +280,9 @@ launch_vscode() {
105280}
106281
107282main () {
108- parse_options " $@ "
283+ local -a unparsed;
284+ parse_options " $@ " unparsed;
285+ set -- " ${unparsed[@]} " ;
109286
110287 # If no CTK/Host compiler are provided, just use the default environment
111288 if [[ -z ${cuda_version:- } ]] && [[ -z ${host_compiler:- } ]]; then
@@ -120,7 +297,7 @@ main() {
120297 fi
121298
122299 if ${docker_mode:- ' false' } ; then
123- launch_docker
300+ launch_docker " $@ "
124301 else
125302 launch_vscode
126303 fi
0 commit comments