Pythonライブラリ開発のための環境構築

# 開発環境の利用目的
* Windows 10 64bit環境で開発する
* 自分の現在の用途はPythonパッケージの作成が主である。
* 処理速度改善のためにC/C++を使ったPythonライブラリも書き、CrossコンパイルしパッケージングしPyPIに上げたりもたまにする。

ということをやるのに、VSCodeは軽量で素晴らしい。自分の中では久々のヒットだ。
ある程度小慣れてきて自分なりの構成が分かったので、PCがぶっ壊れたとき用に環境を再現する手順として備忘しておく。

# この手順で完成する開発環境
* IDE : Visual Studio Code
   * cpp extention
   * python extention
   * markdown extention
   * Git Lense
   * Histroy
   * etc..
* C/C++ : Clang/LLVM
* C/C++ header & library : 最低限のWindows10 SDK & MSVC Build
* python : pyenvでバージョン切替え & poetry
* SCM : Git for Windows
* etc : nodejs, 7zip, sakura editor, wegt, ripgrep, fzf, などを少々

以降の手順は可能な限りコマンドラインで淡々と実施できるよう考えた。
全ての手順はWindows標準のコマンドプロンプトからの入力が前提。

## 1. 環境変数設定

### (1) ユーザ環境変数等
```Batchfile

@rem IDEROOTはインストール先ルートディレクトリのパスをどこにするかまずは指定する。パスは任意です
set IDEROOT=Y:
set PYTHONVERSION=3.9.13
```

* PYTHONVERSIONは グローバル使用するPythonバージョン(pyenvでインストール可能なpythonバージョン`pyenv install --list` であること)
* DATAROOTはプロジェクトデータを保存するディレクトリ
* PYTHONVERSIONは グローバル使用するPythonバージョン(pyenvでインストール可能なpythonバージョン`pyenv install --list` であること)

```Batchfile

powershell -Command "& cd (Get-ChildItem env:IDEROOT).value"
if %errorlevel% neq 0 echo %IDEROOT%が存在しないか、ディレクトリではありません。&& pause && exit

powershell

echo @"
set IDEROOT=$env:IDEROOT

set DATAROOT=%IDEROOT%\usr\local\data

set PYTHONVERSION=$env:PYTHONVERSION

set USRLOCAL=%IDEROOT%\usr\local
set VSCODE_HOME=%USRLOCAL%\vscode
set PYENV_ROOT=%USRLOCAL%\pyenv
set PYENV=%PYENV_ROOT%\pyenv-win
set PYTHONPATH=%PYENV%\versions\%PYTHONVERSION%
set POETRY_HOME=%USRLOCAL%\poetry
set NODEJS_HOME=%USRLOCAL%\nodejs

setx Path `"%PYENV%\bin;%PYENV%\shims;%PYTHONPATH%;%PYTHONPATH%\Scripts;%PYTHONPATH%\Tools\scripts;%POETRY_HOME%\bin;%IDEROOT%\bin;%IDEROOT%\cmd;%IDEROOT%\mingw64\bin;%IDEROOT%\usr\bin;%VSCODE_HOME%\bin;%NODEJS_HOME%;%APPDATA%\npm;%USERPROFILE%\AppData\Local\Microsoft\WindowsApps`"

setx IDEROOT %IDEROOT%
setx DATAROOT %DATAROOT%
setx PYTHONVERSION %PYTHONVERSION%

setx VSCODE_HOME %VSCODE_HOME%
setx PYENV_ROOT %PYENV_ROOT%
setx PYENV %PYENV%
setx PYTHONPATH %PYTHONPATH%
setx POETRY_HOME %POETRY_HOME%
setx NODEJS_HOME %NODEJS_HOME%

"@ | Set-Content $env:IDEROOT\setenv.bat

echo @'
@echo on
%PYTHONPATH%\%~n0.exe %PYTHONPATH%\Scripts\*.exe
pause
'@ | Set-Content $env:IDEROOT\repair_scripts.bat

exit

```
環境変数をセットします。
```Batchfile
%IDEROOT%\setenv.bat

exit

```

## <span style="color: red; ">注意：IDEROOT</span>に既存のディレクトリを指定したら
同名のファイル又はディレクトリがある場合には、上書き良否確認などは一切せず
<span style="color: red; ">強制的に上書き</span>するので
<span style="color: red; ">新規ディレクトリの指定</span>を強く推奨する。


## 2. まず入れるもの

### (1) ダウンロード用の関数準備します
```powershell
mkdir %DATAROOT%
mkdir %IDEROOT%\usr\bin

powershell -ExecutionPolicy RemoteSigned 

# github等 release 最新版URLをゲットする関数とzipをダウンロードしたら任意のパスに解凍し、元のzipファイルは削除する関数を二つ生成する

echo @'
Param(
    [String]$Arg1,
    [String]$Arg2 = '.*(windows|win64|((x86|amd)_?)?64|win32)?.*\.(zip|exe)'
)

Function gitlatest($url, $pattern = $Arg2){
    $url = $url.replace("https://github.com", "https://api.github.com/repos")
    $res = $(curl.exe -sL $url) -replace "`s*`"body`":.*",""
    $res = ($res -join "`n") -replace ",(\n\s*\})", "`$1"
    $j = ConvertFrom-Json "$res"
    $assets = foreach($rl in $j.assets) {
        If($rl.name -match $pattern -and $rl.name -notmatch "darwin|arm"){
            $rl.browser_download_url
        }
    }
    If($assets.Length -gt 0) {
        return $assets[0]
    } else {
        return ""
    }
}

Function nodelatest($url){
    $v = $(curl.exe -sL "https://nodejs.org/dist/index.json" | ConvertFrom-Json)[0].version
    return "https://nodejs.org/dist/$v/node-$v-win-x64.zip"
}

Function getlatest($url, $pattern = $Arg2) {
    If($url -match "https://.*github.com.*") {
        return (gitlatest $url $pattern)
    } elseIf($url -match "https://.*nodejs.org.*") {
        return (nodelatest $url)
    } else {
        return $url
    }
}

getlatest $Arg1 $Arg2

'@ | Set-Content $env:IDEROOT\usr\bin\getlatest.ps1


echo @'
Param(
    [String]$Arg1,
    [String]$Arg2,
    [String]$Arg3 = "*"
)

Function dunzip($expdir, $url, $target = "*"){
    $TMPNAME = "$env:TEMP\workdir_download_will_unzip"
    Remove-Item -Force -Recurse "$TMPNAME*"
    curl.exe -sL -o "$TMPNAME.zip" $url

    if ( !(Test-Path "$TMPNAME.zip") ) {
        echo ダウンロードに失敗しました
        return
    }
    if ( !(Test-Path $expdir) ) {
        mkdir $expdir
    }

    if ( $target -eq "*" ) {
      7z.exe x -o"$expdir" "$TMPNAME.zip" -aoa
    } else {
      7z.exe x -o"$TMPNAME" "$TMPNAME.zip" -aoa
      Copy-Item -Force -Recurse "$TMPNAME\$target" "$expdir\"
    }

    Remove-Item -Force -Recurse "$TMPNAME*"

}

dunzip "$Arg1" "$Arg2" "$Arg3"

'@ | Set-Content $env:IDEROOT\usr\bin\dunzip.ps1


echo "@echo off`npowershell -ExecutionPolicy RemoteSigned -File `"%IDEROOT%\usr\bin\getlatest.ps1`" %*"  | Set-Content $env:IDEROOT\usr\bin\getlatest.cmd
echo "@echo off`npowershell -ExecutionPolicy RemoteSigned -File `"%IDEROOT%\usr\bin\dunzip.ps1`" %*"  | Set-Content $env:IDEROOT\usr\bin\dunzip.cmd


```

以下最新版をインストールします
### (1) [7zip](https://sevenzip.osdn.jp/download.html)
この後の作業で必須

```powershell

curl.exe -sL -o.\7zip.exe https://sourceforge.net/projects/sevenzip/files/latest/download
Start-Process -Verb runas -Wait .\7zip.exe
if ($?) { del .\7zip.exe } else {Write-Error "Install Failed 7zip";return}

$sevenzippath = reg query HKEY_LOCAL_MACHINE\SOFTWARE\7-Zip |select-string Path64
$sevenzippath = [regex]::replace($sevenzippath, '.*[^\s]\s{2,}(.+[^\\])\\?', { $args.groups[1].value })
if (! (Test-Path -Path ${Env:IDEROOT}\usr\bin\7z.exe) ) {Copy-Item $sevenzippath\* -Recurse ${Env:IDEROOT}\usr\bin}

# Set-Item Env:Path "${sevenzippath};${Env:Path}"


```

### (2) [サクラエディタ](https://github.com/sakura-editor/sakura/releases)
好み。なんでもよい

```powershell
$sakuraurl = getlatest "https://github.com/sakura-editor/sakura/releases" ".*Win32-Release-Installer.zip"
curl.exe -sL -o.\sakura.zip $sakuraurl
7z x .\sakura.zip
Start-Process -Verb runas -Wait .\sakura_install*.exe /VERYSILENT
if ($?) { del .\sakura* } else { Write-Error "Install Failed Sakura Editor" }
$sakurapath = reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" /s | select-string -Pattern "InstallLocation.*sakura"
$sakurapath = [regex]::replace($sakurapath, '.*[^\s]\s{2,}(.+[^\\])\\?', { $args.groups[1].value })
Set-Item Env:Path "${Env:Path};${sakurapath}"


```

### (4) [Git for Windows](https://github.com/git-for-windows/git/releases)
Git必須なのと、mingw環境もそこそこ活用するため

```powershell
$giturl = getlatest "https://github.com/git-for-windows/git/releases" "Git.*64-bit.tar."
curl.exe -sL -o.\git-for-windows.tar.bz2 $giturl
echo ${env:IDEROOT}にインストールしてます
7z.exe x .\git-for-windows.tar.bz2 -bsp2
if ($?) { del .\git-for-windows.tar.bz2 }
7z.exe x -o"${Env:IDEROOT}" .\git-for-windows.tar -aoa -bsp2 -x"!dev/fd" -x"!dev/std*" -x"!etc/mtab"
if ($? -And (Test-Path -Path ${Env:IDEROOT}/git-bash.exe) ) { del .\git-for-windows.tar } else {Write-Error "Install Failed Git for windows";return}

# [environment]::SetEnvironmentVariable("Path", $Env:Path, "User")

```

### (5) C/C++ Windows headers & libraries
#### [Microsoft BuildTools](https://visualstudio.microsoft.com/ja/visual-cpp-build-tools/)

##### インストールウィザード手順
1. 個別のコンポーネントタブを選択
2. 検索窓で以下のワードで絞り込む
 * 「MSVC x64 ビルド ツール 最新」
 * 「Windows 10 SDK」
 この2つだけあれば最低限OK。5GBもいるけど、、

```powershell
curl.exe -sL -o ${env:TEMP}\vs_BuildTools.exe https://aka.ms/vs/17/release/vs_BuildTools.exe
Start-Process -Verb runas -Wait "${env:TEMP}\vs_BuildTools.exe"
Remove-Item -Force -Recurse "${env:TEMP}\vs_BuildTools.exe"

```


## 3. 開発環境構築
### (1) セットアップ、セットアップコマンドの作成
1. 後の手順で最新版ダウンロード用のワークシェル
    -> バッチファイルやpowershellは冗長で貧弱で難解すぎて怒りのbash

2. お気に入りのコマンドDL
  a. [fzf](https://github.com/junegunn/fzf#windows)をインストール
  b. [RipGrep](https://github.com/BurntSushi/ripgrep)をインストール
  c. [RipGrep-all](https://github.com/phiresky/ripgrep-all)をインストール

```powershell

## 2. Favorite commands
## fzf
dunzip "$env:IDEROOT\usr\bin" $(getlatest "https://github.com/junegunn/fzf/releases")

## ripgrep
dunzip "$env:IDEROOT\usr\bin" $(getlatest "https://github.com/BurntSushi/ripgrep/releases") ripgrep-*/rg.exe

## jq
curl.exe -sL -o "$env:IDEROOT\usr\bin\jq.exe" $(getlatest "https://github.com/stedolan/jq/releases" "jq-win64.exe")

exit

```


### (2) [Python(pyenv-win)](https://github.com/pyenv-win/pyenv-win)インストール

```Batchfile
git clone https://github.com/pyenv-win/pyenv-win.git "%PYENV_ROOT%"
cd /d "%PYENV_ROOT%"
mv .version pyenv-win
rm -rf [._a-oq-zR]*
mv pyenv-win/.version ./
pyenv --version
pyenv rehash

pyenv install %PYTHONVERSION%
pyenv global %PYTHONVERSION%
pyenv update
pyenv install -l | grep -v "win"

echo python %PYTHONVERSION% の他に必要なバージョンがあれば、ここで入れてください。上に出ているバージョン一覧参照
echo 例: pyenv install 2.7.18


```

### (3) Python(pyenv-win) 初期設定
やってること

1. python関連 SJIS固有の不具合の強引な対策
  * [pyenv Issue51対処](https://github.com/pyenv-win/pyenv-win/issues/51)
  * [python zipファイル名日本語文字化け対策](https://oku.edu.mie-u.ac.jp/~okumura/python/encoding.html)

2. ライブラリインストール
  * pip最新化
  * 開発用ライブラリのインストール
  * CAPI開発用にpythonXX_d.libのインストール

```Batchfile

echo pyenv Issue51関連の不具合修正してます
grep -rl "chcp 1250" * | xargs sed -i.bak "s/chcp 1250/chcp 932/g"
pyenv rehash

powershell -Command "& cd (Get-ChildItem env:IDEROOT).value"

cd /d %PYENV%\versions

for /d %d in (*) do (
  pyenv local %d
  
  echo zipファイル名日本語の文字化け対策をしてます
  sed -i.bak "s/cp437/cp932/g" %PYENV%\versions\%d\Lib\zipfile.py

  echo repair_pathアプリのダウンロード
  curl.exe -sL -o %PYTHONPATH%\repair_scripts.exe https://github.com/kirin123kirin/dotfile/raw/master/python/repair_scripts.exe
  
  echo pipを最新化します
  python -m pip install --upgrade pip
  
  echo 開発用ツールをインストールしてます
  pip install autopep8 scikit-build
  pip install -r https://github.com/scikit-build/scikit-build/raw/master/requirements-dev.txt

  echo debug用ライブラリをダウンロードしてます
  curl.exe -sL -o %TEMP%\dev_d_%d.msi https://www.python.org/ftp/python/%d/amd64/dev_d.msi
  sleep 1
  msiexec /a %TEMP%\dev_d_%d.msi targetdir="%PYENV%\versions\%d" /qn
  del /s /q %TEMP%\dev_d_%d.msi
)

pyenv local %PYTHONVERSION%

python -V

pyenv versions

```

### (4) [poetry](https://github.com/python-poetry/poetry)インストール

```Batchfile
curl.exe -L https://install.python-poetry.org | python -
poetry --version
poetry self update
poetry config --list
poetry config virtualenvs.in-project true
poetry config cache-dir "%POETRY_HOME%\pypoetry\Cache"
poetry config --list

```

### (5) C/C++ Build Tool
#### [LLVM](https://github.com/llvm/llvm-project/releases) & [CMake](https://cmake.org/download/) & [Ninja](https://github.com/ninja-build/ninja/releases)インストール

```Batchfile
echo LLVM インストール...
for /f "tokens=*" %u in ('getlatest https://github.com/llvm/llvm-project/releases win64.exe') do dunzip %IDEROOT% "%u"

rm -rf "%IDEROOT%\$PLUGINSDIR"

echo CMake インストール...
for /f "tokens=*" %u in ('getlatest https://github.com/Kitware/CMake/releases windows-x86_64.zip') do dunzip %IDEROOT% "%u" cmake*/*

echo Ninja インストール...
for /f "tokens=*" %u in ('getlatest https://github.com/ninja-build/ninja/releases win') do dunzip %IDEROOT%\bin "%u"

```

### (6) 必要なら[Node.js](https://nodejs.org/ja/)インストール

```Batchfile
for /f "tokens=*" %u in ('getlatest https://nodejs.org/dist') do dunzip %NODEJS_HOME% "%u" node-v*\*

```

### (7) [VSCode](https://code.visualstudio.com/)インストール

```Batchfile
echo VSCodeインストールしてます
dunzip "%VSCODE_HOME%" "https://code.visualstudio.com/sha/download?build=stable&os=win32-x64-archive"

echo VSCodeのショートカットを作成してます
set TARGET='%VSCODE_HOME%\Code.exe'
set SHORTCUT='%APPDATA%\Microsoft\Windows\Start Menu\Code.lnk'
powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut(%SHORTCUT%); $S.TargetPath = %TARGET%; $S.Save()"
cp %SHORTCUT% %USERPROFILE%\Desktop

```


## 4. 個人的な初期設定
### (1) VSCodeユーザ設定

```Batchfile
echo 拡張機能をインストールします
mkdir %VSCODE_HOME%\data\extensions %VSCODE_HOME%\data\user-data\User
for /f "tokens=*" %x in ('curl.exe -sSL https://raw.githubusercontent.com/kirin123kirin/.vscode/main/vscode_extensions.txt') do code --extensions-dir %VSCODE_HOME%\data\extensions --install-extension %x

echo ステータスバーのダウンロードが完了するまで待ってVSCodeを再起動してください
@code %DATAROOT% && pause

echo 全般設定の設定中
curl.exe -sL -o %VSCODE_HOME%\data\user-data\User\settings.json https://raw.githubusercontent.com/kirin123kirin/.vscode/main/settings.json

echo キーバインドの設定中
curl.exe -sL -o %VSCODE_HOME%\data\user-data\User\keybindings.json https://raw.githubusercontent.com/kirin123kirin/.vscode/main/_keybindings.json


```

### (2) Git config global & PyPI configユーザ設定
ユーザ名とEmailを入力してください。

```shell
echo git configとpypircの設定を行います。

%IDEROOT%\bin\bash.exe

cat<<'EOF' > ~/.gitconfig
[user]
	       email = 
	       name = 
[filter "lfs"]
	       clean = git-lfs clean -- %f
	       smudge = git-lfs smudge -- %f
	       process = git-lfs filter-process
	       required = true
[gui]
	       encoding = utf-8
[core]
        autocrlf = input
EOF

notepad ~/.gitconfig

cat<<'EOF' > ~/.pypirc
[distutils]
index-servers=
    pypi
    pypitest

[pypi]
username: 
password: 

#[pypitest]
#repository: https://test.pypi.org/legacy/
#username : 
#password : 

EOF

notepad ~/.pypirc

exit

```

---
以下は別にやらなくても良い。
### (N) INCLUDE, LIBPATHのユーザ環境変数設定
Windows SDK? Visual Studioのパスが死ぬほどめんどくさいので
無理やり環境変数INCLUDE、LIBPATHをぶち込む

```shell

%IDEROOT%\bin\bash.exe

ARCH=x64
IDEROOT_S=$(echo $IDEROOT | sed "s;\\\;/;g")
WKIT=$(reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots" | grep "KitsRoot" | sed "s;\\\;/;g" | sed -E "s;.*KitsRoot.+\s\s([^\s].+)/$;\1;g")

MSVC_ROOT=$(reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\Setup" | grep "SharedInstallationPath" | sed "s;\\\;/;g" | sed -E "s;.*SharedInstallationPath.+\s\s([^\s].+)/Shared/?$;\1;g")
MSBUILD=`ls -d "$MSVC_ROOT"/[0-9]*/BuildTools | tail -1`
MSVC=`ls -d "$MSBUILD"/VC/Tools/MSVC/[0-9]* | tail -1`
CLANGVERSION=`ls -d "$IDEROOT_S"/lib/clang | tail -1`

INCLUDE=`ls -d "$(ls -d "$WKIT"/Include/[0-9]* | tail -1)"/* | tr '\n' ';'`
INCLUDE="${INCLUDE};${MSVC}/include;$INCLUDE;$MSVC/include;$IDEROOT_S/usr/local/poetry/venv/Include;$IDEROOT_S/lib/clang/${CLANGVERSION}/include;$IDEROOT_S/include"

LIBPATH=`ls -d "$(ls -d "$WKIT"/Lib/[0-9]* | tail -1)"/*/$ARCH | tr '\n' ';'`
LIBPATH="${LIBPATH};${MSVC}/lib/${ARCH};${LIBPATH};$IDEROOT_S/lib/clang/${CLANGVERSION}/lib"
LIBPATH="${LIBPATH};$IDEROOT_S/usr/local/pyenv/pyenv-win/libexec/libs;$IDEROOT_S/usr/lib;$IDEROOT_S/lib"

cat <<EOF > /tmp/setenv.bat
@setx INCLUDE "${INCLUDE}"
@setx LIBPATH "${LIBPATH}"
@setx MSVC "${MSVC//\//\\}"
@setx WKIT "${WKIT//\//\\}"
@setx Path "%Path%;${MSVC//\//\\}\\bin\\Host${ARCH}\\${ARCH}"

EOF

/tmp/setenv.bat && rm /tmp/setenv.bat

cat <<EOF > "$IDEROOT_S"/bin/vsdevcmd.cmd
@echo off
@call "$MSBUILD/Common7/Tools/VsDevCmd.bat" %*
EOF
cp "$IDEROOT_S"/bin/vsdevcmd.cmd "$IDEROOT_S"/bin/vcvarsall.cmd

exit

exit


```

### VSCode Serverを起動できる環境も作りたい場合
```powershell

mkdir %VSCODE_HOME%\resources\server-cli %VSCODE_HOME%\data
mklink /j %USERPROFILE%\.vscode-cli %VSCODE_HOME%\resources\server-cli
mklink /j %USERPROFILE%\.vscode-server %VSCODE_HOME%\data

curl.exe -sL -o %VSCODE_HOME%\bin\code-server.exe https://aka.ms/vscode-server-launcher/x86_64-pc-windows-msvc
echo @echo off > "%VSCODE_HOME%\bin\vscode-server.cmd"
copy %VSCODE_HOME%\data\extensions\extensions.json %VSCODE_HOME%\data\data\User\extensions.json

echo "%~dp0code-server.exe" serve-local --host 0.0.0.0 --port 8090 --without-connection-token %* >> "%VSCODE_HOME%\bin\vscode-server.cmd"

```


## その他
### ドライブマウントについて
コマンドラインからドライブレターをマウントする例(%IDEROOT%をYドライブとしてマウント）

```Batchfile
subst Y: %IDEROOT%

```

しかし再起動したらYドライブへのマウントが消えてしまう。

$IDEROOTをYドライブとして永続的に割り当てたい場合は以下２つの方法がある。

方法1. ユーザ別割り当て

```Batchfile
echo subst Y: "%IDEROOT%" > "%APPDATA%\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\subst.bat"

```

方法2. レジストリにマウント情報保存する例(管理者権限プロンプト）

```Batchfile
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\DOS Devices" /v "E:" /t REG_SZ /d "\??\%IDEROOT%"

```

以上、