Jupyterのcell magicでcellのC/C++ソースをgcc/g++でコンパイルしよう

# 概要

本格的なC/C++プログラミングはVscodeなどIDEを利用して作ることになると思います。しかし、C/C++の勉強やちょっとしたコードの実験などはJupyter notebookが使えると便利です。
Jupyter notebookにはMarkdownが書けるのでちょっとしたコードの勉強や実験などのコメントとコードの両方を書けて、さらに複数のプログラムが同一のnotebook上で見られる利点があると思う。
個人的には別の記事に書いてある通りCERN ROOT Jupyter notebookがおすすめだが、インストールや使い方を覚えるのが面倒だったりする人には簡単にcell magicでコンパイルできれば良いのではないかと思います。また、CERN ROOTはC++17なので、他にC++11,　C++23とか試すには不向きです。

---

- CERN ROOT Jupyer notebook

https://qiita.com/h6591/items/266fa3a6ca3a9a0f98c2


# 実行環境

```terminal
> sw_vers 
ProductName:		macOS
ProductVersion:		15.3.2
BuildVersion:		24D81

> gcc --version
Apple clang version 16.0.0 (clang-1600.0.26.6)
Target: arm64-apple-darwin24.3.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

> python --version
Python 3.13.2
```
# Jupyterでgcc/g++のコンパイル&実行

![gcc_jupyter01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/d4dde315-0f40-48b5-82fe-f3ba32f57378.png)

- `%%script`は元々使えるCell Magicです
- `-xc`, `-xc++` は`-x`の後にコンパイルする言語
    - コンパイルがソースファイルではないのでファイルの拡張子がないため指定する
- `-`はコンパイルするソースが標準入力から渡されることの指定
    - Cellのデータは標準入力渡しになる
- その他は通常のコンパイルオプション
- `!`はLine magicで`!`の後を実行する

---

コンパイルエラーがあると処理プロセスが異常終了したこととなるためPythonのTracebackが表示される
![gcc_jupyter02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/a182412d-1787-42e3-b722-fd4f07d414a3.png)

これを出さないようにしたい。

---

Tracebackを出さないためにscriptのオプション`--no-raise-error`を追加する。
scriptのオプションを見るにはCellの先頭行に次ように`?`を追加して実行する。使い方が表示される。

```python
%%script?
```

Tracebackが出てないことが確認できます。

![gcc_jupyter03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/eeb96ae7-6cf4-4a13-b53e-800e719a6cb8.png)


## Shellスクリプトの作成

`gcc`やTracebackを出させないオプションなどが長くなるのでもっと簡単にするため、自分専用のShellスクリプトを用意します。下記のスクリプトはコンパイルエラーが無かったらバイナリを実行するスクリプトです。

```sh:mygcc
#!/bin/bash
if [ $# -eq 0 ]; then
   NAME="a.out"
else
   NAME="$1"
   shift
fi

g++ -xc++ -  -std=c++11 -o ${NAME} "$@" && ./${NAME}

if [ $? -ne 0 ]; then
    echo
fi
```
スクリプトの引数の1番目をバイナリ名として、残りをコンパイルオプションとします。また、引数がひとつも無いときはバイナリ名を`a.out`とする。そして、コンパイルエラーないときバイナリを実行する。 コンパイルエラーがないと`&&`以降が実行されます。コンパイルエラーがあると**echo**が実行されて、スクリプトが正常終了するのでTracebackが表示されません。

`mygcc`が実行可能なように`chmod u+x mygcc`で変更してください。


![gcc_jupyter04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/9df8a49f-9c0e-4804-9939-4d57aa8ed651.png)


---

エラーが無いソースの実行で、バイナリ名を`cpptest1.out`としてコンパイル＆実行。

![gcc_jupyter05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/7e5eb6c5-85fd-4820-8b45-49d41f128f33.png)

## スクリプトをCell magicとして登録

今度は`%%script`も書くのが面倒なので、上記で作成した`mygcc`をCell magicとして登録します。そうすると、`%%mygcc`として書けます。

登録方法は、IPythonパッケージのあるソースを変更します。ファイルの更新権がない場合はたぶんできないので今までの方法で実行してください。

変更するソースは、`%%script?`で表示された最終行がそのファイル名です。
私の場合は以下のファイル名になります。

```
xxxxxx/site-packages/IPython/core/magics/script.py
```
`script.py`に下記のように`mygcc`を追加します。だいたい、100行目ちょっとの所です。テキストエディタで`bash`か`perl`で文字列検索して場所をみつけています。

```py:script.py
   @default('script_magics')
    def _script_magics_default(self):
        """default to a common list of programs"""
        
        defaults = [
            'sh', 
            'bash',
            'perl',
            'ruby',
            'python',
            'python2',
            'python3', 
            'pypy',
            'mygcc',  # 追加
        ]
```

`restart kernel`でカーネルを再起動をして変更が反映されるようにしてください。

![gcc_jupyter06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/04a86a28-04a5-4e10-b04b-6eedc699bca2.png)


# 終わりに

スクリプトは何個作っても良いのでオプション別に用意するといいと思います。
スクリプト名を分けることでオプションの記述が減ります。

例えば

- gcc汎用  -- mygcc
- gccでxxx Library用  -- myxxxで-I -L -lなどのオプションを記述しておく
- g++汎用  -- mygpp 
- g++でc++11用  -- myg11
- g++でc++17用  -- myg17
- ...

# Appendix

`main`関数内でのコードしか書く必要がなく、`main`内のコードだけ書きたい時も対応した参考例

次のPythonスクリプトを用意します。
`int main`がcellデータに含まれていたらそのまま出力して、含まれていなければヘッダーファイルのincludeとmainを追加して出力するだけの単純な処理です。

```py:source_edit.py
import sys

def print_main(src):
    print("""
    #include <iostream>
    int main() {
    """)
    print(src)
    print("""
        return 0;
    }
    """)

cell_src = sys.stdin.read()
if "int main" in cell_src:
    print(cell_src)
else:
    print_main(cell_src)
```

`g++`の前に`python source_edit.py |`をスクリプトに追加します。

```sh:mygcc
#!/bin/bash
if [ $# -eq 0 ]; then
   NAME="a.out"
else
   NAME="$1"
   shift
fi

python source_edit.py | g++ -xc++ - -std=c++11 -o ${NAME} "$@" && ./${NAME}

if [ $? -ne 0 ]; then
    echo
fi

```



![gcc_jupyter07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/52862f36-f9f2-40df-8b62-08f50511bfe5.png)