ソフトウェア設計論

まつ本

モダンSW開発

モダンSW開発 -ビルド-


目次

・ビルド?建築?
・ビルドツール
・ビルドツールの歴史

・Gradleを試す
・PyBuilderを試す
・ビルドツールと版管理
"X as a Code"

・依存関係の解決
・依存の衝突

・演習

ビルド?建築?

ソースから実行ファイルを得るプロセス

.c.exe
.java.class .jar
.py.pyc
.md.html (この授業資料)

ビルドの主たる作業はコンパイル

C言語 gcc / Java javac / Python compileall

SWは最終的にビルドされてリリースされる

リリースとはソースコード自体の配布ではない
利用者目線ではビルドの結果だけがほしい

ビルドの難しさ

ビルドは本当に大変

単なるコンパイルだけではない

  • 依存:依存関係の解決 / 依存ライブラリの取得
  • 静的解析:脆弱性解析 / 形式チェック / リンタ
  • コンパイル:gcc / javac / compileall
  • テスト:単体・全体の検証 / テスト環境の整備
  • リリース:zipの作成 / 配布サーバへの設置

開発者本人はビルドできるかもしれない

開発者以外は?1年後の自分自身は?

上記のビルド作業をREADMEに記載するのか?
本当に読むか?手で再現できるか?メンテは?

javacの難しさ

javac --classpath で与えるべき情報 (※研究ツールの例)

ビルドツール

ビルドを自動化する仕組み

ビルド作業そのものを機械に実行させる
同じ手順の再現は機械が得意な分野

ビルドは人手でやるべきではない
ミスの温床

各種言語にビルドツールが存在する

C:make
Java:Ant / Maven / Gradle
Python:PyBuilder / tox / Setuptools
JavaSciprt:Vite / ESbuild / webpack
LaTeX:latexmk

Gradleの場合

Gradleによるビルド

$ ./gradlew build
BUILD SUCCESSFUL in 10s

上記buildタスクの依存関係

flowchart LR
  classes --> compileJava & processResources
  javadoc --> classes
  compileTestJava --> classes
  processTestResrouces --> classes
  jar --> classes
  testClasses --> compileTestJava & processTestResrouces
  uploadArchives --> jar
  assemble --> jar
  test --> testClasses & classes
  check --> test
  build --> check & assemble

IDEを使えばよいのでは?

雑談

最近のIDEは賢い

ビルド (の一部) はIDEが助けてくれる
特にコンパイル / テスト / リンタ等

最初に設定しておいて後はIDEに任す
ボタン一つでビルド (の一部) を実行できる

しかしIDEなしでビルドしたい状況も多々ある

例1:テストだけ動かしたい
テストのためだけにIDEを立ち上げるのか?
(IDEの主目的は開発でありビルドではない)

例2:サーバ上でビルドしたい
次回の講義CI/CDへ

headless

雑談

SW開発の基本テクニック

GUIを持たない状態で起動するモードのこと
大きなシステム開発でよく採用されている

$ chrome --headless  # GUIなしでChromeが立ち上がる

as サーバモード

サーバとして起動する場合はGUIがいらない

for テストの軽量化

GUIに無関係なテストはheadlessモードでテスト

for 自動操作

GUIではなくCUI経由で対象SWを動かしたい

ビルドツールの歴史

make (1977~)

命令的 / ビルドツールの始祖

Makefile

CC=gcc
CFLAGS=-O3
SOURCES=main.c functions.c

all: $(SOURCES)

%.o: %.c
    $(CC) $(CFLAGS) $< -o $@

clean:
    rm -rf *.o

コンパイル処理の手順を記述

Ant (2000~)

命令的 / Java用

build.xml

<project name="HelloWorld" basedir="." default="main">
    <property name="src.dir"     value="src"/>
    <property name="build.dir"   value="build"/>
    <property name="classes.dir" value="${build.dir}/classes"/>

    <target name="clean">
        <delete dir="${build.dir}"/>
    </target>

    <target name="compile">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}"/>
    </target>
    ...

書式がXMLになっただけで本質はmakeと同じ

Maven (2004~) / Gradle (2008~)

宣言的 (命令を列挙しない)

build.gradle

plugins {
    application
}
dependencies {
    testImplementation(libs.junit.jupiter) // JUnit5のこと
    coding(libs.guava)             // GoogleのLib
}
application {
    mainClass = "org.example.App"
}
...

手続きではなく規約を書く (設定ではない)
CoCパラダイムに従う

CoC

Convention over configuration

「設定より規約」というパラダイム

開発者は規約に従わない部分だけを宣言する
規約≒デフォルトと考えると分かりやすい

Gradleの規約の一例

アプリコードは src/main/java の配下に設置する
テストコードは src/test/java の配下に設置する
ビルド結果は build/classes/java

規約化できない要素 (設定が必要な事項)

依存関係 / mainクラスの名前 / ...

モダンSW開発 -ビルド-


目次

・ビルド?建築?
・ビルドツール
・ビルドツールの歴史

・Gradleを試す
・PyBuilderを試す
・ビルドツールと版管理
"X as a Code"

・依存関係の解決
・依存の衝突

・演習

Gradleを試す

プロジェクトの生成

$ mkdir gradle-trial; cd gradle-trial
$ gradle init  # gradleプロジェクトの生成

Welcome to Gradle 8.14!
...
Select type of build to generate:
  1: Application
  2: Library
  3: Gradle plugin
  4: Basic (build structure only)
Enter selection (default: Application) [1..4]
...

各種規約の変更を対話的に行う
変更がなければEnter連打 (ここではJavaアプリを選択)

生成結果の確認

$ tree ./app  # アプリのルートを確認
app
├── build.gradle.kts  # アプリビルド方法の規約 (依存宣言等)
└── src               # src以下がソースコード
    ├── main
    │   ├── java
    │   │   └── org
    │   │       └── example
    │   │           └── App.java  # アプリのコード
    │   └── resources             # アプリのリソース (.prop等)
    └── test
        ├── java
        │   └── org
        │       └── example
        │           └── AppTest.java  # テストコード
        └── resources

ビルドツールに規約が存在する
=規約に従うプロジェクトを自動で生成できる

ソースコードを確認

$ cat app/src/main/java/org/example/App.java
package org.example;

public class App {
    public String getGreeting() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        System.out.println(new App().getGreeting());
    }
}

簡単なJavaのサンプルコードが生成されている

テストコードを確認

$ cat app/src/test/java/org/example/AppTest.java
package org.example;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class AppTest {
    @Test void appHasAGreeting() {
        App classUnderTest = new App();
        assertNotNull(classUnderTest.getGreeting(),
          "app should have a greeting");
    }
}

簡単なJavaのサンプルテストコード

規約通りのプロジェクトが生成できた
あとはIDEを使って開発を進めれば良い

app以外の生成ディレクトリを確認

$ tree -I app  # app以外を確認
.
├── gradle
│   ├── libs.versions.toml  # バージョンカタログ (今日は略)
│   └── wrapper                       # wrapper関係
│       ├── gradle-wrapper.jar        # wrapper関係
│       └── gradle-wrapper.properties # wrapper関係
├── gradle.properties                 # wrapper関係
├── gradlew                           # wrapper関係
├── gradlew.bat                       # wrapper関係
└── settings.gradle.kts  # プロジェクト全体の設定 (略)

ほとんどはwrapper周り

wrapperって?

gradle未インストールでもgradleを利用できる

$ gradle build     # インストールしたgradleを使ってビルド
BUILD SUCCESSFUL in 754ms
7 actionable tasks: 7 up-to-date
$ ./gradlew build  # wrapperを使ってビルド (↑と同じ結果)

プロジェクトの環境依存を最小限にできる
開発者が指定したverのgradleを実行できる

裏側ではgradleのjarをDLして実行してるだけ

$ ./gradlew build
Downloading https:.../gradle-8.14-bin.zip
.............10%.............20%.........   # DL開始
BUILD SUCCESSFUL in 12s
7 actionable tasks: 7 up-to-date

インストールと実行

雑談

「インストールしてから実行」という常識

WindowsもAndroidもMacもLinuxも同じ

しかしインストールは目的ではない

利用者がやりたいことはインストールではない
インストールは目的のための手段
目的は実行

💡実行時に暗黙的にインストールする

利用者は目的だけを明示する $ ./gradlew build
npxも似たアイデア $ npx tree-node-cli
Webアプリも似ている http://maps.google.com
今後似た仕組みが増えるかもしれない

PyBuilderを試す

pyプロジェクトの生成

$ mkdir py-trial; cd py-trial
$ pyb --start-project
Project name (default: 'py-trial') :
Source directory (default: 'src/main/python') :
Docs directory (default: 'docs') :
Unittest directory (default: 'src/unittest/python') :
Scripts directory (default: 'src/main/scripts') :
Use plugin python.flake8 (Y/n)? (default: 'y') :
Use plugin python.coverage (Y/n)? (default: 'y') :
...

簡単なソースコードを追加

$ echo 'print("hi")' > src/main/python/main.py

ビルド

$ pyb
...
Build finished at 2025-05-05 13:42:14
Build took 4 seconds (4787 ms)

ビルド結果の確認

$ tree target/dist
target/dist
└── py-trial-1.0.dev0
    ├── main.py
    ├── scripts
    └── setup.py

PyBuilderの利用プロジェクトは多くはない
おすすめというわけではない

Pythonではデファクトがまだなさそう
選択肢が多い (根本的にpyはビルドが楽)

ビルドツールと版管理

ソース/テストだけでなくビルド方法も版管理へ

共同開発で特に強力
ビルドの再現性の確保

ビルド方法の進化も版の一種

ビルドスクリプトを起因としたバグもある
版管理しておけばロールバックも可能

ビルドスクリプトの同時編集にも耐えられる

CI/CDとの相性の良さ

サーバ上での自動ビルドが可能
ビルド方法を機械処理可能とするうまみ

"X as a code"

ある対象Xをコード化する考え

コード化=自動化可

一種のエンジニアリング

対象Xの属人性を排除できる
再現性も高い

Xの一例

Infrastructure (IaC)
Configuration (CaC)
Requirements (RaC)
Security (SaC)
テストもコード化できる

コード化 ≒ バグとの戦い

雑談

LaTeXのビルドツール

雑談

LaTeXのコンパイルは大変

$ platex x    # tex       → aux(壊れ) + dvi(壊れ)
$ bibtex x    # bib + aux → bbl
$ platex x    # tex + bbl → aux       + dvi(壊れ)
$ platex x    # tex + bbl → aux       + dvi
$ dvipdfmx x  # dvi       → pdf

Latexmk

$ latexmk x   # これだけでOK
$ cat .latexmkrc  # 設定ファイルは必要 (Antに近い)
$latex = 'platex -synctex=1 %O %S';
$bibtex = 'bibtexu %O %B';
$dvipdf = 'dvipdfmx %O -o %D %S';
...

モダンSW開発 -ビルド-


目次

・ビルド?建築?
・ビルドツール
・ビルドツールの歴史

・Gradleを試す
・PyBuilderを試す
・ビルドツールと版管理
"X as a Code"

・依存関係の解決
・依存の衝突

・演習

依存関係の解決

Dependency resolution

ビルド作業の一部
プログラム間の依存関係を認識して解決する

依存の例 (Pythonプロジェクト):

MyApp  # 実験用の行列計算スクリプト (結果はSlackにポスト)
 ├─ pandas                # 直接依存
 │   ├── numpy              # 推移的依存
 │   ├── python-dateutil    # 推移的依存
 │   │    └── six           # 推移的依存
 │   ├── pytz               # 推移的依存
 │   └── tzdata             # 推移的依存
 └─ requests              # 直接依存
     ├── charset-normalizer # 推移的依存

SW開発や利用における古典的な問題

依存関係

直接依存と推移的依存

「XはYを利用する」 (直接依存 X → Y)
「YがZを利用する」 (推移的依存 X → Y → Z)

各SWのリリース版は依存SWを内包していない
依存を宣言しているだけ
(zip内にまとめられているわけではない)

推移的依存を再帰的に手繰らないといけない

バージョン情報も解決すべき依存の一種

「XはYのv2.0以上を利用する」 X → Y(>=2.0)

verが変わると振る舞いが変わる可能性がある
SWは常に互換性があるわけではない

依存関係の宣言と解決の例

Pythonの場合

pipがPythonの標準パッケージマネージャ
(pipenvやpoetryなどもある)

依存関係の宣言

$ cat requirements.txt  # 直接依存の宣言
pandas>=2.2    # v2.2以上ならOK
requests       # vはなんでもOK (自動的に最新を取得)

pipにより依存関係を解決

$ pip install -r requirements.txt
Collecting pandas>=2.2 (from -r requirements.txt (line 1))
Collecting requests    (from -r requirements.txt (line 2))
...

解決した依存関係を表示

$ pipdeptree  # 依存関係を表示
pandas==2.2.3
├── numpy              [required: >=1.26.0, installed: 2.2.5]
├── python-dateutil    [required: >=2.8.2,  installed: 2.9.0]
│   └── six            [required: >=1.5,    installed: 1.17.0]
├── pytz               [required: >=2020.1, installed: 2025.2]
└── tzdata             [required: >=2022.7, installed: 2025.2]
requests==2.32.3
├── charset-normalizer [required: >=2,<4,   installed: 3.4.2]
├── idna               [required: >=2.5,<4, installed: 3.10]
...

依存解決=requiredを満たす最新verを自動特定
一種の充足可能問題 (SAT)

各言語のSWリポジトリから自動取得される

依存の衝突

要求する依存に矛盾がある
つまり充足可能問題を解けない状況のこと

衝突するように依存を変更

$ cat requirements.txt
pandas==2.2.3  # 2.2.3のみに変更
requests
six==1.2  # sixの1.2への依存を宣言 (pandas==2.2.3と矛盾)

(再掲)

$ pipdeptree  # 依存関係を表示
pandas==2.2.3
├── numpy           [required: >=1.26.0, installed: 2.2.5]
├── python-dateutil [required: >=2.8.2, installed: 2.9.0]
│   └── six         [required: >=1.5, installed: 1.17.0]
                                # ↑こことの矛盾

衝突の確認

$ pip install -r requirements.txt
...
ERROR: Cannot install pandas and six==1.2 because
these package versions have conflicting dependencies.

The conflict is caused by:
    The user requested six==1.2
    python-dateutil 2.9.0.post0 depends on six>=1.5
    The user requested six==1.2
    python-dateutil 2.9.0 depends on six>=1.5
    The user requested six==1.2
    python-dateutil 2.8.2 depends on six>=1.5

どの依存 (要求) と衝突するかも表示してくれる

SWは機能的豊富さだけでなく依存の柔軟さも重要

  • 制約の強いver依存を書かないべき
  • 極力新しいverに依存すべき

SW開発外でのパッケージマネージャ

SW開発外でも広く使われる

インストールのコード化が可能
DL + 依存関係解決 + ビルド + 設置

新規PC立ち上げ時に強力

Linux / BSD系

apt / yum / dpkg

Mac

Homebrew

Windows

Chocolatey

ビルドツールまとめ

積極的に使おう

自動化の恩恵
版管理・CI/CDとの相性が良い

規約のうまみ
規約は一種の共通認識ともいえる

依存関係

必ずパッケージマネージャを使おう
既存libを積極的に使おう
(バグを減らす一つの方法はコードを書かないこと)

柔軟な依存を宣言しよう
強い制約はSWの柔軟性を損なう (可搬性とも呼ぶ)

モダンSW開発 -ビルド-


目次

・ビルド?建築?
・ビルドツール
・ビルドツールの歴史

・Gradleを試す
・PyBuilderを試す
・ビルドツールと版管理
"X as a Code"

・依存関係の解決
・依存の衝突

・演習

演習 (10m)

Pythonのパッケージマネージャpoetryを試せ

※ 各ステップのLinuxコマンドは作業方法の一例である
  実施すべき作業は完全に同じコマンドである必要はない点に注意

提出方法

作業1~8の実行コマンドとその結果を以下書式のテキストにまとめ,CLEに提出すること

# 1
...

# 2
...

0. 前準備

$ cd <work>                  # 適当な作業フォルダへ移動
$ pip install poetry         # poetryのインストール
$ poetry new <project-name>  # プロジェクト生成
$ cd <project-name>          # 移動

1. 生成されたファイル一覧を確認せよ

$ find .  # 全ファイルを再帰的に表示
???

2. ソースとテストを設置すべき場所を考えよ

$ touch ???/main.py       # ソースを???へ設置
$ touch ???/test_main.py  # テストを???へ設置

3. pyproject.tomlの中身を確認せよ

$ cat pyproject.toml
???

4. 2つの依存関係を宣言せよ

  • pandas>=2.0.0を利用する
  • requestsを利用する (verは指定なし)

依存の宣言方法は自身で調べよ

$ poetry add ???
$ poetry add ???

5. 再度pyproject.tomlの中身を確認せよ

$ cat pyproject.toml
???

6. 解決された依存一覧を確認せよ

$ poetry show
???

7. プロジェクトをビルドしその結果を確認せよ

$ poetry build
$ find .
???

8. 依存を衝突させエラー内容を確認せよ

pandas == 2.2.3とsix == 1.2の利用を宣言せよ
これは矛盾する依存である (講義資料のP34を参照)

$ poetry add ???
???
$ poetry add ???
???

%%{init:{'theme':'base','themeVariables':{'primaryColor':'#6A7FAB','primaryTextColor':'#FAFBF9','primaryBorderColor':'#6A7FAB','lineColor':'#6A7FABCC','textColor':'#6A7FABCC','fontSize':'20px','padding':0}}}%% %%{ init: { 'flowchart': { 'nodeSpacing': 10, 'rankSpacing': 100 } } }%%

![](https://docs.gradle.org/current/userguide/img/task-dag-examples.png) <subb>https://docs.gradle.org/current/userguide/build_lifecycle.html</subb>

規約に従うなら何も書かなくて良い

--- # バージョンロック ## 宣言した依存が常に正とは限らない `pandas>=2.0`という依存を宣言して開発を続ける いつのまにか`p --- 依存解決自体は標準でpip gradle 作法の理解にも繋がる `src/main/java` ? 版管理との組み合わせ ソース テスト ビルド方法 自己完結 自動化が可能