失敗談:VScodeでワンクリックでElectronのNativeAddonをデバッグしたかった

ネガティブインフォメーションも価値があるよね。

## やりたかったこと

- 環境はWindowsかMac、両方で開発。
- IDEは問わない。
- ボタンひとつ押すだけでElectronを作成->C++のコードをビルド->Electron立ち上げ->C++をデバッグ
- C++のコードを必ずデバッグするようにすると時間がかかるので、その部分だけビルドしたり飛ばしたりしたい。
- 自動で既に起動している特定のプロセスをアタッチ（デバッグ）したい。

## 出来たこと

- Electronをビルド->Electronを立ち上げ
- Electronを作成->C++をビルド->Electron立ち上げ。
- IDEはCLionとVScodeである程度は出来た。
- Process IDを指定すれば、Electron起動後にC++のコードをアタッチしてデバッグ可能。でもいちいちProcess IDを特定するのが面倒。

## 出来なかったこと

- Electronは起動するとMainとRendererで別のプロセスが起動し、Renderer側にC++を実行させたのだが、複数のRendererが立ち上がっているので「どれか１つを特定して」アタッチさせる。

## VSCodeでやってみたこと

以下のファイルをプロジェクトの直下において、適当に`npm install`した。

```json:package.json
{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build:dev": "./node_modules/.bin/node-gyp rebuild --target=5.0.8 --arch=x64 --dist-url=https://electronjs.org/headers --debug"
  },
  "devDependencies": {
    "electron": "5.0.8",
    "electron-prebuilt": "^1.4.13",
    "electron-rebuild": "^1.8.5",
    "node-gyp": "5.0.0",
    "rebuild": "^0.1.2"
  },
  "dependencies": {}
}

```

```c++:test.cc
#include <node.h>
#include <v8.h>
#include <string.h>
#include <memory>

int data = 0;

void Method(const v8::FunctionCallbackInfo <v8::Value> &args) {
    v8::Isolate *isolate = args.GetIsolate();
    v8::HandleScope scope(isolate);
    std::string strings = "world" + std::to_string(data);
    args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, strings.c_str()));
}

void CountUp(const v8::FunctionCallbackInfo <v8::Value> &args) {
// このへんにブレークポイント設置。
    data = data + 1;
}

void init(v8::Local <v8::Object> exports) {
    NODE_SET_METHOD(exports, "hello", Method);
    NODE_SET_METHOD(exports, "countUp", CountUp);
}

NODE_MODULE(binding, init
);
```

```html:index.html
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>test</title></head>
<body>
<div id='test'></div>
<button onclick="testFunc()">test button</button>
<button onclick="test2()">Count up</button>
<script>
    var remote = require('electron').remote;
    var testC = require('./build/Debug/testFunction');
    function test2(){
        testC.countUp();
    }
    function testFunc(){
        document.getElementById('test').innerText = "hello " + testC.hello();
    }
</script>
</body>
</html>
```

```js:index.js
"use strict";
const electron = require("electron");
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
let mainWindow;

app.on('window-all-closed', function() {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

app.on('ready', function() {
  mainWindow = new BrowserWindow({
      width: 800, height: 600, title: 'test',darkTheme:true,
      webPreferences: {
        nodeIntegration: true
  }
  });
  mainWindow.on('page-title-updated', (evt) => {
    evt.preventDefault();
  });
  mainWindow.webContents.openDevTools();
  mainWindow.loadURL('file://' + __dirname + '/index.html');
  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});

```

```json:binding.gyp
{
  "targets": [
    {
      "target_name": "testFunction",
      "sources": [
        "test.cc"
      ]
    },
  ]
}
```

### launch.json

実行環境はMac
VSCodeのビルドとかデバッグは、「.vscode/launch.json」に記述してます。
やってみた結果とかをコメントで書いてます。

```json:launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "electron start",
            "type": "node",
            "request": "launch",
            "cwd": "${workspaceRoot}",
            "program": "${workspaceRoot}/index.js",
            // 実行前にpackage.jsonで指定したビルドを実行
            "preLaunchTask": "npm: build:dev",
            // Electronを指定
            "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
            // 子プロセスもアタッチしてみた（ただしlldbではなくnode.jsとして）
            "autoAttachChildProcesses":true,
            "runtimeArgs": [
                "--enable-logging",
                "--debug=5858",
                "--remote-debugging-port=9223",
            ],
            "args": [
                ".",
            ],
            "windows": {
                "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
            },
            "console": "internalConsole",
        },
      { 
          "name": "native addon",
          // lldbでアタッチ（Mac用）
          "type": "lldb",
          // 実行前にC++をビルド
          "preLaunchTask": "npm: build:dev",
          // attachに指定すると、以下の"program"が有効になる。
          "request": "attach",
          // ↓process name が Electron Helperだからこれでアタッチできるんじゃね？->1個に絞れと怒られた。
          // "program": "pgrep -n 'Electron Helper'",
          // ↓ダメ元でプログラムの位置を指定してみた->ダメだった。
          // "program": "/Applications/Visual Studio Code.app/Contents/MacOS/Electron",
          // "program": "${workspaceRoot}/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron",
          // "pid": 7669,
          // でも上記が上手く行かなかったので手動でprocess idを指定することに・・・
          "pid":"${command:pickMyProcess}",
          "expressions": "native",
      },
      {
        "name": "native addon custom (error)",
        // lldbでアタッチ（Mac用）
        "type": "lldb",
        // customに指定すると、lldbの細かい動作を指定できる。。
        "request": "custom",
        "preLaunchTask": "npm: build:dev",
        // １つに絞れと怒られる。
        "targetCreateCommands":["process attach --name \"Electron Helper\""],
        // pgrepでprocess idを表示はできるが、結局何処に使えば・・・
        // pgrep 'Electron Helper'
        // shellで実行出来なくはない。でもその値をlldbに持っていけない
        // "targetCreateCommands":["platform shell pgrep 'Electron Helper'"],
        // process id一覧表示
        // "targetCreateCommands":["platform list"],
        // 配列で複数の内容を記述も可能。
        // "targetCreateCommands":["env ELECTRON_HELPER='Electron Helper'",
            // "attach --name 'Electron Helper'",
            // "platform shell pgrep 'Electron Helper'",
            // "attach ${command:pickProcess}",
            // "attach --name '${input:pickProgram}'"
        //],
        // target createでファイルでの指定はできるが、Electronは一度に複数のHelperを起動するので、その全てをどうやってか指定する必要がある。
        // "targetCreateCommands":["target create 'path'"],
        "expressions": "native",
      }
    ],
    // ちなみに、自分で作ったリストを表示させる方法もあるが、pidは起動ごとに変わるので意味はないかな。
    "inputs": [
      {
          "id": "pickData",
          "description": "select",
          "type": "pickString",
          "options": ["Electron Helper","Electron"],
          "default": "Electron Helper"
      }
  ]
  }
  
  
```


## 参考

https://github.com/vadimcn/vscode-lldb/blob/v1.2.3/MANUAL.md
https://electronjs.org/docs/development/debugging-instructions-macos
https://yokaze.github.io/2018/01/06/