VSCode 上での競技プログラミングの環境構築 (C++)

## はじめに

この記事では VSCode 上に C++ での競技プログラミングの環境を構築し、AtCoder や Codeforces 等のコンテストに出る際、ファイルの用意から提出までをサポートできるようにする方法を紹介します。

## 競技プログラミング用ワークスペースの作成

適当な競プロ用のディレクトリ(ここでは `contests` とします)を作成します。
特に言及しない限り、記事内ではこのディレクトリをカレントディレクトリとします。

## C++ の環境構築

`C++` のインストールについて書かれた記事は大量にあるので、適当に調べて自分の環境に合わせたものをインストールすれば良いです。
Ubuntu であれば build-essential パッケージを多くの場合インストール済みだと思います。これに `g++` が同梱されているので、特に何も追加でやらなくて問題ないです。

この記事では `g++` で説明していきますが、他の `clnag++` でも大きく変わらないと思うので、そちらを使う場合は適宜読み替えてください。

### VSCode における C++ の設定

[C/C++](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) をインストールします。
これによって、VSCode 上で簡単にビルドやデバッグ、補完が行えるようになります。
ただし、この記事ではデバッグについては特に説明せず、コードの補完についてはより強力な `clangd` を用いるので、基本的にはビルドのためにインストールしています。

#### コード補完の設定

[clangd](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd) をインストールします。
これによって、強力なコードの補完が行えるようになります。
他にも色々な機能があるので、興味のある方は以下の記事を参照してみてください。

https://qiita.com/TumoiYorozu/items/0ca14c65559e693f9e89

インストールの際に、先程導入した [C/C++](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) の IntelliSense を無効化するか聞かれると思うので、無効化します。
その後 `clnagd` を導入したことが無ければ、インストールするかどうかを聞かれると思うので、そのまま `Install` を押してインストールします。

ワークスペース内で補完が効くようにするため `.clnagd` というファイルを作成して、以下のようにします。

```yaml:.clangd
If:
  PathMatch: .*

CompileFlags:
  Add:
    - --target=x86_64-linux-gnu
    - --include-directory=/usr/include/c++/11
    - --include-directory=/usr/include/x86_64-linux-gnu/c++/11
    # コンパイル時に使用しているものがあれば、ここに追記します
  Compiler: g++
```

環境によって、多少設定する値は変わるので、そのあたりは適当に合わせてください。
また `.clangd` では、上記で挙げた記事にもあるように、様々な設定ができるので、好みで設定を追加してください。
追加できる設定については、以下を参照してください。

https://clangd.llvm.org/config

:::note info
`CompileFlags` で外部ライブラリのインクルードを行う等、ディレクトリやファイルの指定を行うときは、絶対パスで記述してください。
相対パスには対応していません。
:::

#### フォーマッタの設定

ClangFormat を使ってコードの整形をできるようにしていきます。
`.clang-format` ファイルを作成して、以下に従ってルールを作成していきます。

https://clang.llvm.org/docs/ClangFormatStyleOptions.html

<details><summary>参考</summary>

参考程度に、私の設定ファイルを置いておきますが、好みが分かれると思うので、自分で作成する方が良いと思います。

```yaml:.clang-format
---
# BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: BlockIndent
AlignArrayOfStructures: Right
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignConsecutiveMacros: None
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
  - __capability
BinPackArguments: false
BinPackParameters: false
BitFieldColonSpacing: Both
BraceWrapping:
  AfterCaseLabel: false
  AfterClass: false
  AfterControlStatement: Never
  AfterEnum: false
  AfterFunction: false
  AfterNamespace: false
  AfterObjCDeclaration: false
  AfterStruct: false
  AfterUnion: false
  AfterExternBlock: false
  BeforeCatch: false
  BeforeElse: false
  BeforeLambdaBody: false
  BeforeWhile: false
  IndentBraces: false
  SplitEmptyFunction: true
  SplitEmptyRecord: true
  SplitEmptyNamespace: true
BreakAfterJavaFieldAnnotations: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeConceptDeclarations: true
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
BreakStringLiterals: false
ColumnLimit: 88
CommentPragmas: "^ IWYU pragma:"
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
  - foreach
  - Q_FOREACH
  - BOOST_FOREACH
IfMacros:
  - KJ_IF_MAYBE
IncludeBlocks: Regroup
IncludeCategories:
  - Regex: ^"(llvm|llvm-c|clang|clang-c)/
    Priority: 2
    SortPriority: 0
    CaseSensitive: false
  - Regex: ^(<|"(gtest|gmock|isl|json)/)
    Priority: 3
    SortPriority: 0
    CaseSensitive: false
  - Regex: .*
    Priority: 1
    SortPriority: 0
    CaseSensitive: false
IncludeIsMainRegex: (Test)?$
IncludeIsMainSourceRegex: ""
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: Indent
IndentGotoLabels: false
IndentPPDirectives: None
IndentRequires: false
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
Language: Cpp
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PPIndentWidth: -1
PackConstructorInitializers: NextLine
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
QualifierAlignment: Right
ReferenceAlignment: Pointer
ReflowComments: false
RemoveBracesLLVM: false
SeparateDefinitionBlocks: Always
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: Both
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
  AfterControlStatements: true
  AfterForeachMacros: true
  AfterFunctionDeclarationName: false
  AfterFunctionDefinitionName: false
  AfterIfMacros: true
  AfterOverloadedOperator: false
  BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
  Minimum: 1
  Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++17
StatementAttributeLikeMacros:
  - Q_EMIT
StatementMacros:
  - Q_UNUSED
  - QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: ForContinuationAndIndentation
WhitespaceSensitiveMacros:
  - STRINGIZE
  - PP_STRINGIZE
  - BOOST_PP_STRINGIZE
  - NS_SWIFT_NAME
  - CF_SWIFT_NAME
```

</details>

作成したら `.vscode` ディレクトリ内の `.settings.json` に `"C_Cpp.clang_format_style": "file"` を追記して、作成したファイルからフォーマット時のルールを読み込むようにします。
こだわりがなければ `"C_Cpp.clang_format_style": "Google"` 等とデフォルトで用意されているものから選んで設定する方が楽です。

また、保存時にコードが整形されるようにすると便利なので `.settings.json` に `"editor.formatOnSave": true` も追記します。
これにより `Ctrl`+`S` で整形されるようになります。

ここでやったことをまとめると、以下のものが `.vscode/.settings.json` に追記されます。

```json:.vscode/.settings.json
{
    "C_Cpp.clang_format_style": "file",
    "editor.formatOnSave": true
}
```

## コンテスト用環境の構築

ここからは、問題を解く際のファイルやディレクトリを用意したり、テストケースでの実行を行ったり、コードの提出を行ったり、することを補助するための環境を構築していきます。

### online-judge-tools の設定

https://github.com/online-judge-tools

ここでは `online-judge-tools/oj` と `online-judge-tools/template-generator` の2つを導入します。
これらについての説明は、以下のURLを見てください。

- https://github.com/online-judge-tools/oj/blob/master/docs/getting-started.ja.md
- https://github.com/online-judge-tools/template-generator/blob/master/README.ja.md

導入方法についてもそれぞれの GitHub に書いてありますが、この記事でも一応記述しておきます。
これらのツールの導入のために `pip` 等を使えるようにしてください。
`pip` 等のインストールについての説明は省略します。

また、この記事では `uv` を使ってインストールするので、それ以外の場合は適宜読み替えてください。

#### oj のインストール

https://github.com/online-judge-tools/oj

下記のコマンドでインストールします。

```sh
uv add online-judge-tools
```

##### oj でのログイン

コンテストサイトにログインをします。
これによって、コンテスト用のディレクトリの作成や、コードの提出が自動で行えるようになります。

```sh
uv run oj login https://atcoder.jp
```

上記のコマンドを入力すると、AtCoder でのユーザー名とパスワードを求められるので入力します。
`[SUCCESS] You have already signed in.` と言うような表示が出れば大丈夫です。

#### template-generator のインストール

基本的には以下に従います。

https://github.com/online-judge-tools/template-generator/blob/master/README.ja.md#how-to-install

下記のコマンドでインストールします。

```sh
uv add online-judge-template-generator
```

#### VSCode における online-judge-tools の設定

提出等を簡単に行えるようにするため、VSCode のタスクを定義していきます。

##### シェルスクリプトの作成

タスクとして使いやすいように、コマンドの塊をシェルスクリプトとして定義していきます。

まず `configs` ディレクトリを作成し、その中にシェルスクリプトを入れていきます。

コードの提出を行うためのスクリプトを `submit.sh` という名前でファイルを作成します。
そのファイルに以下のように書きます。

```sh:configs/submit.sh
#!/bin/bash

submit_file=$1 # relative file path (${service_name}/${contest_id}/${problem_id}/main.cpp)

service_name=${submit_file%%/*}
contest_id=$(basename ${submit_file%/*/*})
problem_id=$(basename ${submit_file%/*})

case "$service_name" in
  "AtCoder" )
    problem_url="https://atcoder.jp/contests/${contest_id}/tasks/${problem_id}"
    ;;
  "Codeforces" )
    problem_url="https://codeforces.com/contest/${contest_id}/problem/${problem_id}"
    ;;
esac

# 環境に応じて書き換える
uv run oj s -y ${problem_url} ${submit_file}
```

その後 `chmod 755 configs/submit.sh` を実行してパーミッションを変更します。

同じように、テストケースのダウンロードを行うためのスクリプトを `dltest.sh` という名前でファイルを作成し、以下のように記述して、パーミッションを変更します。

```sh:configs/dltest.sh
#!/bin/bash

problem_dir=$1 # relative directory path (${service_name}/${contest_id}/${problem_id})

service_name=${problem_dir%%/*}
contest_id=$(basename ${problem_dir%/*})
problem_id=${problem_dir##*/}

test_dir=${problem_dir}/test

case "$service_name" in
  "AtCoder" )
    problem_url="https://atcoder.jp/contests/${contest_id}/tasks/${problem_id}"
    ;;
  "Codeforces" )
    problem_url="https://codeforces.com/contest/${contest_id}/problem/${problem_id}"
    ;;
esac

if [ ! -e ${test_dir} ]; then
    # 環境に応じて書き換える
    uv run oj d -d ${test_dir} ${problem_url}
fi
```

##### テンプレート等の作成

`configs/` ディレクトリに `prepare.config.toml` を以下の内容で作成します。
以下に従って適当に追記すると良いです。

https://github.com/online-judge-tools/template-generator/blob/master/README.ja.md#oj-prepare

```toml:configs/prepare.config.toml
contest_directory = "{service_name}/{contest_id}"
problem_directory = "{problem_id}"

[templates]
"main.cpp" = "my_main.cpp"
```

そして `~/.config/online-judge-tools/template/` ディレクトリに `my_main.cpp` を作成します。
これがテンプレートファイルになるので、使いやすいように変更してください。
以下に従ってテンプレートを作成すると、入出力まで自動で書かれたファイルが生成されます。

https://github.com/online-judge-tools/template-generator/blob/master/README.ja.md#oj-template

スタイル等にこだわりがなく、初期のままで良ければ、テンプレートファイルを作る必要はありませんので `prepare.config.toml` をそのようにしてください。
組み込みのテンプレートファイル `main.cpp` では入出力まで自動で生成されるようになっています。

<details><summary>テンプレートの例(入出力自動化無し)</summary>

```cpp:~/.config/online-judge-tools/template/my_main.cpp
<%!
	import os
	from logging import getLogger

	import onlinejudge_template.generator.cplusplus as cplusplus
	import onlinejudge_template.generator.topcoder as topcoder
	import onlinejudge_template.generator.hook as hook
%>\
<%
	logger = getLogger(__name__)
	data["config"]["indent"] = "\t"
	data["config"]["scanner"] = "cin"
	data["config"]["printer"] = "cout"
	hook.register_filter_command(["sed", "-e", "s/std:://g", "-e", r"s/'\\n'/endl/g"], data=data)
%>\
#include <iostream>

using namespace std;

int main() {

	return 0;
}
```

</details>

#### タスクの登録

`.vscode` ディレクトリに `tasks.json` を作成し、以下のように記述して保存します。
自身の環境に合わせてコマンドや引数の部分を書き換えてください。
特にコンパイルオプションの部分はかなり好みが出ると思うので、見直すと良いと思います。

これは少し長いので折りたたんでおきます。

<details><summary>タスクの登録</summary>

```json:.vscode/tasks.json
{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "cppbuild",
			"label": "build",
			"command": "/usr/bin/g++",
			"args": [
				"-std=c++17",
				"-O2",
				"-pedantic-errors",
				"-Wall",
				"-fsanitize=undefined,address",
				"-g",
				"${file}",
				"-o",
				"${fileDirname}/a.out"
			],
			"problemMatcher": [
				"$gcc"
			],
			"group": {
				"kind": "build",
				"isDefault": true
			},
			"detail": "compiler: /usr/bin/g++",
			"presentation": {
				"focus": true
			}
		},
		{
			"type": "shell",
			"label": "download test cases",
			"command": "${workspaceFolder}/configs/dltest.sh",
			"args": [
				"${relativeFileDirname}"
			]
		},
		{
			"type": "shell",
			"label": "do oj-prepare",
			"command": "uv",
			"args": [
				"run",
				"oj-prepare",
				"--config-file",
				"configs/prepare.config.toml",
				"${input:service_url}/${input:contest_id}"
			],
			"problemMatcher": []
		},
		{
			"type": "shell",
			"label": "do oj test",
			"command": "uv",
			"args": [
				"run",
				"oj",
				"t",
				"-c",
				"${fileDirname}/a.out",
				"-d",
				"${fileDirname}/test"
			],
			"dependsOn": [
				"build",
				"download test cases"
			],
			"group": {
				"kind": "test",
				"isDefault": true
			},
			"presentation": {
				"focus": true
			}
		},
		{
			"type": "shell",
			"label": "submit",
			"command": "${workspaceFolder}/configs/submit.sh",
			"args": [
				"${relativeFile}"
			]
		}
	],
	"inputs": [
		{
			"id": "service_url",
			"type": "pickString",
			"description": "Contest site URL",
			"options": [
				"https://atcoder.jp/contests",
				"https://codeforces.com/contest"
			],
			"default": "https://atcoder.jp/contests"
		},
		{
			"id": "contest_id",
			"type": "promptString",
			"description": "Contest ID"
		}
	]
}
```

</details>

`Ctrl`+`Shift`+`P` を押して `Tasks: Run Task` を選択すると、登録したタスクが出てきて、選択するだけで実行できるようになっています。

一応、使用するタスクを説明しておくと、

- `do oj-prepare`: コンテストのディレクトリ作成とテストケースのダウンロード
- `do oj test`: 入力例などによるテスト
- `submit`: コードの提出

となっています。
挙げていない他のタスクを直接使用することは、あまりないと思います。
また、始まっていないコンテストで `do oj-prepare` はできないので注意してください。

タスクをショートカット一発で実行できるようにする場合は `keybindings.json` に適当に追記すればよいです。
私は `Ctrl`+`Shift`+`T` でテストが行えるように設定しています。

あとは実際に問題を解いて提出してみてください。

### オプション: 競プロ用ライブラリの include

競プロ用のライブラリを持っており、使う度にコピペをしているという場合は [online-judge-tools/verification-helper](https://github.com/online-judge-tools/verification-helper/blob/master/README.ja.md) の [oj-bundle](https://github.com/online-judge-tools/verification-helper/blob/master/README.ja.md#include-%E3%81%AE%E8%87%AA%E5%8B%95%E5%B1%95%E9%96%8B) を導入すると、コーディング体験がかなり良くなると思います。
これを利用すると include guard を考慮した上で include を自動で展開してくれます。

#### 競プロ用ライブラリのコピー

競プロ用のライブラリを git 等で管理している場合は clone をします。
もし、このコンテスト用のディレクトリを git で管理しようと思っているのであれば submodule の方がよいかもしれません。
git 等で管理していないのであれば、適当にリンクを張ってください。
ここでは競プロ用ライブラリのディレクトリ名を `library` と置いて説明していきます。

### clangd の修正

補完でライブラリのコードが出るように `.clangd` ファイルを修正します。

```diff_yaml:.clnagd
@@ -6,4 +6,5 @@
     - --target=x86_64-linux-gnu
     - --include-directory=/usr/include/c++/11
     - --include-directory=/usr/include/x86_64-linux-gnu/c++/11
+    - -I<projectDirectoryAbsolutePath>/library
   Compiler: g++
```

`<projectDirectoryAbsolutePath>` のところは適宜変えてください。
環境によっては `target` や `include-directory` の部分も全然違うと思うので、その場合も適宜変更してください。
前述したように、パスは絶対パスで記述しないと読み込まれません。

<details><summary>修正後のファイル</summary>

`-I` の後に、ライブラリまでのパスを絶対パス書けば良いです。

```yaml:.clangd
If:
  PathMatch: .*

CompileFlags:
  Add:
    - --target=x86_64-linux-gnu
    - --include-directory=/usr/include/c++/11
    - --include-directory=/usr/include/x86_64-linux-gnu/c++/11
    - -I<projectDirectoryAbsolutePath>/library
  Compiler: g++
```

</details>

### online-judge-tools/verification-helper のインストール

https://github.com/online-judge-tools/verification-helper/blob/master/README.ja.md

このツールに含まれている `oj-bundle` は、自作ライブラリから include したものを一つのファイルにまとめる機能があります。
これによって、一つのファイルしか提出できないコンテストサイトでも、問題なく提出できるようになります。

下記のコマンドでインストールします。

```sh
uv add online-judge-verify-helper
```

### シェルスクリプトの追加作成と修正

コードを一つのファイルにまとめるためのスクリプトを `bundle.sh` という名前でファイルを作成し、以下のように記述して、パーミッションを変更します。
`oj-bundle` を使うと `#line ...` のような表示や、余分な空行がでてくるので、それらを消す処理も入れています。

```sh:configs/bundle.sh
#!/bin/bash

file_dir_name=$1
file_basename_no_extension=$2
file_extname=$3

file=${file_dir_name}/${file_basename_no_extension}${file_extname}
bundled_file=${file_dir_name}/${file_basename_no_extension}_bundled${file_extname}

uv run oj-bundle -I library ${file} \
| grep -v '^#line ' \
| cat -s \
| sed -e '1{/^$/d}' \
| clang-format --style=file \
> ${bundled_file}
```

`configs/submit.sh` がバンドル前のファイルを提出するようになっているので、バンドル後のファイルを提出するように修正します。

```diff_shell:configs/submit.sh
@@ -1,10 +1,15 @@
 #!/bin/bash
 
-submit_file=$1 # relative file path (${service_name}/${contest_id}/${problem_id}/main.cpp)
+file_dir_name=$1
+file_basename_no_extension=$2
+file_extname=$3
 
-service_name=${submit_file%%/*}
-contest_id=$(basename ${submit_file%/*/*})
-problem_id=$(basename ${submit_file%/*})
+file=${file_dir_name}/${file_basename_no_extension}${file_extname}
+bundled_file=${file_dir_name}/${file_basename_no_extension}_bundled${file_extname}
+
+service_name=${file%%/*}
+contest_id=$(basename ${file%/*/*})
+problem_id=$(basename ${file%/*})
 
 case "$service_name" in
   "AtCoder" )
@@ -15,4 +20,4 @@ case "$service_name" in
     ;;
 esac
 
-uv run oj s -y ${problem_url} ${submit_file}
+uv run oj s -y ${problem_url} ${bundled_file}
```

<details><summary>修正後のファイル</summary>

```sh:configs/submit.sh
#!/bin/bash

file_dir_name=$1
file_basename_no_extension=$2
file_extname=$3

file=${file_dir_name}/${file_basename_no_extension}${file_extname}
bundled_file=${file_dir_name}/${file_basename_no_extension}_bundled${file_extname}

service_name=${file%%/*}
contest_id=$(basename ${file%/*/*})
problem_id=$(basename ${file%/*})

case "$service_name" in
  "AtCoder" )
    problem_url="https://atcoder.jp/contests/${contest_id}/tasks/${problem_id}"
    ;;
  "Codeforces" )
    problem_url="https://codeforces.com/contest/${contest_id}/problem/${problem_id}"
    ;;
esac

uv run oj s -y ${problem_url} ${bundled_file}
```

</details>

#### タスクの修正

`tasks.json` を修正して、バンドルしたファイルを提出するようにします。

```diff_json:.vscode/tasks.json
@@ -12,6 +12,8 @@
                                "-Wall",
                                "-fsanitize=undefined,address",
                                "-g",
+                               "-I",
+                               "./library",
                                "${file}",
                                "-o",
                                "${fileDirname}/a.out"
@@ -79,7 +81,22 @@
                        "label": "submit",
                        "command": "${workspaceFolder}/configs/submit.sh",
                        "args": [
-                               "${relativeFile}"
+                               "${relativeFileDirname}",
+                               "${fileBasenameNoExtension}",
+                               "${fileExtname}"
+                       ],
+                       "dependsOn": [
+                               "do oj-bundle"
+                       ],
+               },
+               {
+                       "type": "shell",
+                       "label": "do oj-bundle",
+                       "command": "${workspaceFolder}/configs/bundle.sh",
+                       "args": [
+                               "${relativeFileDirname}",
+                               "${fileBasenameNoExtension}",
+                               "${fileExtname}"
                        ]
                }
        ],
```

<details><summary>修正後のファイル</summary>

```json:.vscode/tasks.json
{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "cppbuild",
			"label": "build",
			"command": "/usr/bin/g++",
			"args": [
				"-std=c++17",
				"-O2",
				"-pedantic-errors",
				"-Wall",
				"-fsanitize=undefined,address",
				"-g",
				"-I",
				"./library",
				"${file}",
				"-o",
				"${fileDirname}/a.out"
			],
			"problemMatcher": [
				"$gcc"
			],
			"group": {
				"kind": "build",
				"isDefault": true
			},
			"detail": "compiler: /usr/bin/g++",
			"presentation": {
				"focus": true
			}
		},
		{
			"type": "shell",
			"label": "download test cases",
			"command": "${workspaceFolder}/configs/dltest.sh",
			"args": [
				"${relativeFileDirname}"
			]
		},
		{
			"type": "shell",
			"label": "do oj-prepare",
			"command": "uv",
			"args": [
				"run",
				"oj-prepare",
				"--config-file",
				"configs/prepare.config.toml",
				"${input:service_url}/${input:contest_id}"
			],
			"problemMatcher": []
		},
		{
			"type": "shell",
			"label": "do oj test",
			"command": "uv",
			"args": [
				"run",
				"oj",
				"t",
				"-c",
				"${fileDirname}/a.out",
				"-d",
				"${fileDirname}/test"
			],
			"dependsOn": [
				"build",
				"download test cases"
			],
			"group": {
				"kind": "test",
				"isDefault": true
			},
			"presentation": {
				"focus": true
			}
		},
		{
			"type": "shell",
			"label": "submit",
			"command": "${workspaceFolder}/configs/submit.sh",
			"args": [
				"${relativeFileDirname}",
				"${fileBasenameNoExtension}",
				"${fileExtname}"
			],
			"dependsOn": [
				"do oj-bundle"
			],
		},
		{
			"type": "shell",
			"label": "do oj-bundle",
			"command": "${workspaceFolder}/configs/bundle.sh",
			"args": [
				"${relativeFileDirname}",
				"${fileBasenameNoExtension}",
				"${fileExtname}"
			]
		}
	],
	"inputs": [
		{
			"id": "service_url",
			"type": "pickString",
			"description": "Contest site URL",
			"options": [
				"https://atcoder.jp/contests",
				"https://codeforces.com/contest"
			],
			"default": "https://atcoder.jp/contests"
		},
		{
			"id": "contest_id",
			"type": "promptString",
			"description": "Contest ID"
		}
	]
}
```

</details>

これで提出時にバンドルされたファイルが提出されるようになりました。

## おわりに

これらの環境構築を行うと、問題を解くこと以外に取られる時間はかなり減ると思います。
面倒だなと感じる箇所があればどんどん変えていって、自分にとってやりやすい環境を作っていきましょう！

今回紹介したツール以外にも、競プロ用の便利なツール等がたくさんあるので、吟味してみると良いかもしれません。