Posted at

Xamarin.Androidプロジェクトをテキストエディタで作ってみよう

More than 3 years have passed since last update.

これは技術書典#1でTechBoosterが発行するAndroid本向けにわたしが書いているXamarin.Android SDKネタ、のスピンオフのようなものです。何となくどんな内容になるのか、雰囲気を感じ取って興味を持ってもらえればと思います。


MSBuildはじめの一歩

Xamarin.Androidプロジェクトは、Xamarin StudioあるいはVisual StudioでサポートされているMSBuild形式に従っています。

MSBuildは、JavaにおけるAntのような、XML形式のビルド記述方式を採用しています。

最も簡単なMSBuildスタイルのビルドスクリプトを書いてみましょう。簡単な内容なので、テキストエディタで編集できます。

~$ cat x.proj 

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target>
<Message Text="Hello, MSBuild" />
</Target>
</Project>

ビルドしてみましょう。

~$ xbuild x.proj /nologo

Build started 5/25/2016 2:55:08 PM.
__________________________________________________
Project "/home/atsushi/Desktop/ManualProject/ManualProject/x.proj" (default target(s)):
Target :
Hello, MSBuild
Done building project "/home/atsushi/Desktop/ManualProject/ManualProject/x.proj".

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.0672030

どうでしょう? catで示された通り、内容はメッセージを表示するだけのビルドターゲットを含んでいます。http://schemas.microsoft.com/developer/msbuild/2003というXML名前空間さえ覚えておけば、何とか手書きでも作れる内容ではないでしょうか。

MSBuildは、ターゲットが1つしかない場合は、そのターゲットを実行します。複数ある場合は、「最初のターゲット」が実行されます。通常は、ターゲットにName属性で名前を付けます。

~$ cat x.proj 

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="x">
<Message Text="Hello, MSBuild" />
</Target>
<Target Name="y">
<Message Text="Hello, MSBuild 2" />
</Target>
</Project>

ビルドしてみましょう。

~$ xbuild x.proj /nologo

Build started 5/25/2016 3:08:28 PM.
__________________________________________________
Project "/home/atsushi/Desktop/ManualProject/ManualProject/x.proj" (default target(s)):
Target x:
Hello, MSBuild
Done building project "/home/atsushi/Desktop/ManualProject/ManualProject/x.proj".

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.0638150

ただし、同じ名前のターゲットは、後から内容を差し替えることが出来てしまうので、このサンプルのTargetをコピーして下に追加した上で、内容を変更すると、その内容が(最初のターゲットの内容を書き換えて)実行されます。

~$ cat x.proj 

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target>
<Message Text="Hello, MSBuild" />
</Target>
<Target>
<Message Text="Hello, MSBuild 2" />
</Target>
</Project>

ビルドしてみましょう。

~$ xbuild x.proj /nologo

Build started 5/25/2016 3:09:55 PM.
__________________________________________________
Project "/home/atsushi/Desktop/ManualProject/ManualProject/x.proj" (default target(s)):
Target :
Hello, MSBuild 2
Done building project "/home/atsushi/Desktop/ManualProject/ManualProject/x.proj".

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.0639620


C#のプロジェクト

さて、通常われわれはC#/.NETのプロジェクトを作ります(F#でもかまいませんが、今日は言語によって内容が変わるので、C#に固定します)。C#プロジェクトは、通常C#プロジェクトをビルドするために必要なMicrooft.CSharp.targetsというファイルをインポートして作成します。いったんこのファイルをインポートすると、Compileというビルドアイテムで、C#ソースファイルを指定することが出来ます。

~$ cat y.proj 

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Include="test.cs" />
</ItemGroup>

<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
</Project>

このインポートを宣言した後は、もうターゲットを定義する必要はありません。このインポートされるファイルの中で定義されているためです。一方、ItemGroupという新しい要素が出てきましたが、これはビルドに使用される各種ファイルを指定するために使用される、MSBuildで定義されている要素です。ここでは、test.csというファイルがCompileという要素で定義されていますね。このItemGroupの内容は、「ビルド アクション」を要素名に、ファイル名をInclude属性に、それぞれ指定したものになります。

~$ cat test.cs

public class Test
{
public static void Main ()
{
System.Console.WriteLine ("Hello, C#");
}
}

ではビルドして実行してみましょう。

~$ xbuild y.proj /nologo

Build started 5/25/2016 3:14:14 PM.
__________________________________________________
Project "/home/atsushi/Desktop/ManualProject/ManualProject/y.proj" (default target(s)):
Target PrepareForBuild:
Configuration: Debug Platform: AnyCPU
Target GenerateSatelliteAssemblies:
No input files were specified for target GenerateSatelliteAssemblies, skipping.
Target GenerateTargetFrameworkMonikerAttribute:
Skipping target "GenerateTargetFrameworkMonikerAttribute" because its outputs are up-to-date.
Target CoreCompile:
Skipping target "CoreCompile" because its outputs are up-to-date.
Done building project "/home/atsushi/Desktop/ManualProject/ManualProject/y.proj".

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.1992210
~$ mono bin/Debug/y.exe
Hello, C#

簡単に出来ましたね。ちなみに、アセンブリ参照を追加したい場合は、これもItemGroupの内容として追加します。要素名はReferenceで、ファイル名はパス無しのアセンブリ名で大丈夫です。

  <ItemGroup>

<Compile Include="MainActivity.cs" />
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>

プロジェクトファイルの全容は省略しましたが、この部分以外は先の例と同じです。Linqを使ってみましょう。

~$ cat test.cs

using System.Linq;
public class Test
{
public static void Main ()
{
System.Console.WriteLine ("Hello, C#".Last ());
}
}

~$ xbuild y.proj /nologo

Build started 5/25/2016 3:29:15 PM.
__________________________________________________
Project "/home/atsushi/Desktop/ManualProject/ManualProject/y.proj" (default target(s)):
Target PrepareForBuild:
Configuration: Debug Platform: AnyCPU
Target GenerateSatelliteAssemblies:
No input files were specified for target GenerateSatelliteAssemblies, skipping.
Target GenerateTargetFrameworkMonikerAttribute:
Skipping target "GenerateTargetFrameworkMonikerAttribute" because its outputs are up-to-date.
Target CoreCompile:
Skipping target "CoreCompile" because its outputs are up-to-date.
Done building project "/home/atsushi/Desktop/ManualProject/ManualProject/y.proj".

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.2009900
~$ mono bin/Debug/y.exe
#

無事動きました。


Xamarin.Androidのプロジェクト

さて、いよいよXamarin.Androidプロジェクトを作ってビルドしてみましょう。少しややこしくなりますが、(ほぼ)最小の構成は以下のようになるでしょう。

~$ cat ManualProject.csproj 

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition="'$(Configuration)'==''">Debug</Configuration>
<OutputType>Library</OutputType>
<!-- AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk -->
<AssemblyName>ManualProject</AssemblyName>
<AndroidApplication>True</AndroidApplication>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<OutputPath>bin\Debug</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<OutputPath>bin\Release</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="MainActivity.cs" />
<Reference Include="System" />
<Reference Include="Mono.Android" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project>

新しくPropertyGroupという要素が出現しましたが、これはMSBuildプロパティを定義するために使われる、MSBuildが最初から用意している要素です。この中に、プロジェクトのオプションなどがXML要素として格納されます。

OutputType(ライブラリまたは実行形式), OutputPath(出力パス), RootNamespace(デフォルトのC#ネームスペース), AssemblyName(出力アセンブリ名)は、実のところ通常のC#プロジェクトでも使われるものです。OutputPathは、通常のC#プロジェクトでは「無くても良い」のですが、Xamarin.Androidでは必要になります。

Xamarin.Androidプロジェクトでは、インポートされるのはXamarin.Android.CSharp.targetsになります。これは、Androidプロジェクトをビルドするために必要なビルド構成要素を定義しているものです。

このプロジェクトでは、使用するAndroidのAPI Levelを指定していないのですが、その場合は(現在は)10になります。

apkをビルドする際には、ただのBuildターゲットではなく、SignAndroidPackageを呼び出します。

~/$ xbuild ManualProject.csproj /t:SignAndroidPackage /nologo >/dev/null

~/$ find . -name *.apk
./bin/Debug/ManualProject.ManualProject.apk
./bin/Debug/ManualProject.ManualProject-Signed.apk
./obj/Debug/android/bin/ManualProject.ManualProject.apk

どうでしょうか? Xamarin.Androidプロジェクトも、割と簡単に出来てしまいましたね。こんな感じで、IDEが無い環境でも、Androidプロジェクトを作ることは、一応は可能です。