しかし、私たちは解決策を見つけました-今ではテストなしでリポジトリにコミットすることは不可能です。少なくともいつの間にか、そして免責されて。
テストシステム
最初に必要なのはテストシステムです。すでにここで説明しました。Ciサーバー上でローカルに実行するには同じコードが必要であるため、保守に問題がないことを思い出してください。プロジェクトが一般的なテストのためにさまざまなパラメータを設定できること、またはさらに良いことに、プロジェクトを独自のパラメータで拡張できることが望ましいです。もちろん、キャンディーはすぐには出てきませんでした。
ステージ1-走ることはできますが、痛いです。Pythonコードをどうするかはまだ明らかですが、CppCheck、Bloaty、optipng、内部松葉杖、自転車など、あらゆる種類のユーティリティを使用しています。正しく実行するには、同僚が作業するすべてのプラットフォーム(Mac、Windows、Linux)用の実行可能ファイルが必要です。この段階では、必要なすべてのバイナリがリポジトリにあり、バイナリフォルダへの相対パスがテストシステム設定に示されていました。
<CppCheck bin_folder=”utils/cppcheck”>...</CppCheck>
これにはいくつかの問題があります。
- プロジェクト側からは、不要なファイルは各開発者のコンピューターで必要になるため、リポジトリに保存する必要があります。当然、このためリポジトリは大きくなります。
- 問題が発生した場合、必要な構造がフォルダー内にあるかどうかにかかわらず、プロジェクトのバージョンを理解することは困難です。
- 必要なバイナリはどこで入手できますか?自分でコンパイルし、インターネットでダウンロードしますか?
ステージ2-ユーティリティで物事を整理します。しかし、必要なすべてのユーティリティを書き出して1つのリポジトリに収集するとどうなるでしょうか。アイデアは、サーバー上に、必要なすべてのプラットフォーム用にすでにアセンブルされたユーティリティがあり、それらもバージョン管理されているということです。すでにNexusSonatypeを使用していたので、次の部門に行き、ファイルについて合意しました。結果は構造です:
開始するには、バイナリがどこにあるかを知っているスクリプトが必要です。バイナリをダウンロードし、プラットフォームに応じて、渡されたパラメータを使用して実行することもできます。
実装の複雑さを省く
def get_tools_info(project_tools_xml, available_tools_xml): # Parse available tools at first and feel up dictionary root = etree.parse(available_tools_xml).getroot() tools = {} # Parse xml and find current installed version ... return tools def update_tool(tool_info: ToolInfo): if tool_info.current_version == tool_info.needed_version: return if tool_info.needed_version not in tool_info.versions: raise RuntimeError(f'Tool "{tool_info.tool_id}" has no version "{tool_info.needed_version}"') if os.path.isdir(tool_info.output_folder): shutil.rmtree(tool_info.output_folder) g_server_interface.download(tool_id=tool_info.tool_id, version=tool_info.needed_version, output_folder=tool_info.output_folder) def run_tool(tool_info: ToolInfo, tool_args): system_name = platform.system().lower() tool_bin = tool_info.exe_infos[system_name].executable full_path = os.path.join(tool_info.output_folder, tool_bin) command = [full_path] + tool_args try: print(f'Run tool: "{tool_info.tool_id}" with commands: "{" ".join(tool_args)}"') output = subprocess.check_output(command) print(output) except Exception as e: print(f'Fail with: {e}') return 1 return 0 def run(project_tools_xml, available_tools_xml, tool_id, tool_args): tools = get_tools_info(project_tools_xml=project_tools_xml, available_tools_xml=available_tools_xml) update_tool(tools[tool_id]) return run_tool(tool_info, tool_args)
サーバー上に、ユーティリティの説明を含むファイルを追加しました。このファイルのアドレスは変更されていないので、最初にそこに行って在庫があるかどうかを確認します。微妙な点を省いて、これらは各プラットフォームのパッケージ名とパッケージ内の実行可能ファイルへのパスです。
「サーバー上の」xml
<?xml version='1.0' encoding='utf-8'?>
<Tools>
<CppCheck>
<windows executable="cppcheck.exe" />
<darwin executable="cppcheck" />
<linux executable="cppcheck" />
</CppCheck>
</Tools>
そして、プロジェクトに、必要なものの説明を含むファイルを追加します。
xmlプロジェクト
, , , . .
:
, — .
- , , ? -, . — , . : git.
-, — bash-, git: pull push, , git-.
, :
, , , .git/hooks. — . , ( Windows Mac), . , .
, . , .
. , , git-bash Windows. FAQ.
. , . , FAQ. , .git/hooks . , :
, , :
— . .git/hooks, . . , .git/hooks , .
, , - . , -. — . , — . :
, : , . , .
<spoiler title=« :> : 32- ; , 64-; pip install , . - 32- — .
<?xml version='1.0' encoding='utf-8'?>
<Tools>
<CppCheck version="1.89" />
</Tools>
, , , . .
python -m utility_runner --available-source D:\Playrix\![habr]\gd_hooks\available_source.xml --project-tools D:\Playrix\![habr]\gd_hooks\project\project_tools.xml --run-tool CppCheck -- --version
:
- , ,
- , , . .
, — .
- ?
- , , ? -, . — , . : git.
-, — bash-, git: pull push, , git-.
, :
- pre-commit — . , .
- prepare-commit-msg — , . , rebase.
- commit-msg — . , . , .
, , , .git/hooks. — . , ( Windows Mac), . , .
, . , .
. , , git-bash Windows. FAQ.
: , , dns . , curl
[ .
. , . , FAQ. , .git/hooks . , :
git rev-parse
git rev-parse --git-path hooks
, , :
|
|
Worktree |
|
submodule |
|
— . .git/hooks, . . , .git/hooks , .
, , - . , -. — . , — . :
- pre-commit , . pre-commit-tmp
- commit-msg pre-commit pre-commit-tmp
, : , . , .
<spoiler title=« :> : 32- ; , 64-; pip install , . - 32- — .
しかし、それでも、どのように起動するのですか?
最初に、クロワッサンがよりおいしい、どのpythonをインストールするかについての複数ページの指示を作成しました 。しかし、ゲームデザイナーとスクランブルエッグについて覚えていますか?それは常に燃え尽きています:間違ったビット数のpython、または3.7ではなく2.7。そして、これはすべて、ユーザーが作業する2つのプラットフォーム(WindowsとMac)によっても倍増されます。 (私たちと一緒にいるLinuxユーザーは、すべてを自分で設定し、タンバリンの音を静かにタップするか、問題
を解決しました。) 問題を根本的に解決しました。必要なバージョンとビット数のPythonを収集しました。そして、「どのようにそれを置き、どこに保管するか」という質問に対して、彼らは答えました:ネクサス!唯一の問題:Nexusからユーティリティを実行するために作成したPythonスクリプトを実行するためのPythonがまだありません。
そして、それがbashの出番です!彼はそれほど怖くはなく、あなたが彼に慣れればさらに良いです。そしてそれはどこでも機能します:unixではすべてがすでに問題なく、Windowsではgit-bashと一緒にインストールされます (これはローカルシステムの唯一の要件です)。インストールアルゴリズムは非常に単純です。
- 必要なプラットフォーム用にコンパイルされたPythonアーカイブをダウンロードします。これを行う最も簡単な方法は、カールを使用することです。ほとんどどこにでもあります(Windowsでも)。
Pythonをダウンロードmkdir -p "$PYTHON_PRIMARY_DIR" curl "$PYTHON_NEXUS_URL" --output "$PYTHON_PRIMARY_DIR/ci_python.zip" --insecure || exit 1
- それを解凍し、ダウンロードしたバイナリにリンクする仮想環境を作成します。間違いを繰り返さないでください。virtualenvバージョンを釘付けにすることを忘れないでください。
echo "Unzip python..." unzip "$PYTHON_PRIMARY_DIR/ci_python.zip" -d "$PYTHON_PRIMARY_DIR" > "unzip.log" rm -f "$PYTHON_PRIMARY_DIR/ci_python.zip" echo "Create virtual environment..." "$PYTHON_EXECUTABLE" -m pip install virtualenv==16.7.9 --disable-pip-version-check --no-warn-script-location
- lib / *のライブラリが必要な場合は、自分でコピーする必要があります。virtualenvはそれについて考えていません。
- 必要なすべてのパッケージをインストールします。ここで、プロジェクトにci / required.txtファイルを含めることに同意しました。このファイルには、すべての依存関係がpip形式で含まれています。
依存関係のインストール
OUT_FILE="$VENV_DIR/pip_log.txt" "$PYTHON_VENV_EXECUTABLE" -m pip install -r "$REQUIRED_FILE" >> "$OUT_FILE" 2>&1 result=$? if [[ "$result" != "0" ]]; then var2=$(grep ERROR "$OUT_FILE") echo "$(tput setaf 3)" "$var2" "$(tput sgr 0)" echo -e "\e[1;31m" "Error while installing requirements. More details in: $OUT_FILE" "\e[0m" result=$ERR_PIP fi exit $result
Required.txtの例
pywin32==225;sys_platform == "win32" cryptography==3.0.0 google-api-python-client==1.7.11
彼らが問題に取り組むとき、彼らは通常、エラーが表示されたコンソールのスクリーンショットを添付します。作業を簡単にするために、最後のpipインストール実行の出力を保存するだけでなく、 色を追加して、ログからコンソールに直接色のエラーを表示します。長生きするgrep!
見た目
一見、仮想環境は必要ないように思えるかもしれません。結局のところ、私たちはすでに別のバイナリを別のディレクトリにダウンロードしています。システムがデプロイされているフォルダーが複数ある場合でも、バイナリーは異なります。だが! Virtualenvには、Pythonをグローバル環境にあるかのように呼び出すことができるようにするアクティブ化スクリプト があり ます。これにより、スクリプトの実行が分離され、起動が容易になります。 想像してみてください。Pythonスクリプトが実行され、別のPythonスクリプトが実行されるバッチファイルを実行する必要があります。これは架空の例ではありません。これは、アプリケーションをビルドするときにビルド後のイベントが実行される方法です。 virtualenvがなければ、必要なパスをその場でどこでも計算する必要がありますが、 アクティブ化すると
どこでもPythonを使用してい ます。より正確には、 vpython-コンソールとスクリプトの両方から簡単に実行できるように、独自のラッパーを追加しました。シェルでは、既にアクティブ化された環境にあるかどうか、TeamCity(仮想環境がある場所)で実行されているかどうかを確認すると同時に、環境を準備します。
vpython.cmd
set CUR_DIR=%~dp0 set "REPO_DIR=%CUR_DIR%\." rem VIRTUAL_ENV is the variable from activate.bat and is set automatically rem TEAMCITY - if we are running from agent we need no virtualenv activation if "%VIRTUAL_ENV%"=="" IF "%TEAMCITY%"=="" ( set RETURN=if_state goto prepare :if_state if %ERRORLEVEL% neq 0 ( echo [31m Error while prepare environment. Run ci\PrepareAll.cmd via command line [0m exit /b 1 ) call "%REPO_DIR%\.venv\Scripts\activate.bat" rem special variable to check if venv activated from this script set VENV_FROM_CURRENT=true ) rem Run simple python and forward args to it python %* SET result=%errorlevel% if "%VENV_FROM_CURRENT%"=="true" ( call "%REPO_DIR%\.venv\Scripts\deactivate.bat" set CI_VENV_RUN= set VENV_FROM_CURRENT= ) :eof exit /b %result% :prepare setlocal set RUN_FROM_SCRIPT=true call "%REPO_DIR%\ci\PrepareEnvironment.cmd" > NUL endlocal goto %RETURN%
タナカン、またはテストを置くことを忘れないでください
テストを実行する際の忘却の問題は解決しましたが、スクリプトが1つでも見落とされる可能性があります。したがって、彼らは物忘れのためにピルを作りました。それは2つの部分に分かれています。
システムが起動すると、コミットコメントが変更され、「承認済み」としてマークされます。ラベルとして、私たちは哲学を使わず、コメントの最後に[+]または[-]をコミットに追加することにしました。
メッセージを解析するスクリプトがサーバー上で実行されており、切望されている文字のセットが見つからない場合は、作成者のタスクが作成されます。これは、最も単純で最も洗練されたソリューションです。印刷できない文字は明らかではありません。サーバーフックを実行するには、GitHubで別の料金プランが必要であり、1つの機能に対してプレミアムを購入する人は誰もいません。コミットの履歴を調べ、シンボルを探し、タスクを設定することは明らかであり、それほど費用はかかりません。
はい、あなたはあなた自身のペンでシンボルを置くことができます、しかしあなたはサーバー上のアセンブリを壊さないことを確信していますか?そして、あなたがそれを壊すならば...はい、Homescapesからのハゲ男はすでにあなたをフォローしています。
収益は何ですか
フックが検出したエラーの数を追跡することはかなり困難です-それらはサーバーに到達しません。より多くのグリーンアセンブリがあるという主観的な意見だけがあります。ただし、マイナス面もあります。コミットにかなりの時間がかかり始めました。場合によっては最大10分かかることもありますが、それは最適化についての別の話です。