1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

スマホアプリのコマンドビルドまとめ(Xamarin編)

Last updated at Posted at 2022-06-03

前回の『スマホアプリのコマンドビルドまとめ(iOS編)』に引き続き、今回はXamarinのCI環境を作る際に溜まった知見をまとめた記事です。

前提として、Mac mini/Mac Pro(ゴミ箱)などにCI環境を作り、その際にXamarinアプリの.ipa/.apkファイルをビルドする環境も作るときに必要な作業として淡々とまとめています。

環境

  • macOS
  • CI
    • Jenkins
  • Xamarin
    • MyApp.iOS.csproj
    • MyApp.Droid.csproj

msbuildxbuildについて

XamarinのアプリをCLIからビルドしたいと思い、『xamarin mac build command』という感じでググっているとMSBuildxbuildについてちょいちょい出てきます。Xamarinなので記事が古いものが多く引っかかり、個人的に参考になった記事の中で@fenrir_officialさんが以下の記事内容で次のように開設されています。

Xamarin のアプリを Jenkins などでビルドする場合、コマンドラインからビルドしたいことがあると思います。.NET 系のエンジニアには、.csproj ファイルは MSBuild というビルドエンジンのビルド構成ファイルそのものであるというのはよく知られており、これを msbuild コマンドや、xbuild コマンドに与えることでビルドができます。

今回は、Mac で xbuild コマンドを使う方法で、Xamarin.iOS / Xamarin.Android のアプリをビルドする方法をご紹介します。

もちろん、2年前の記事なので当時はmsbuildではなく、xbuildでビルドする手法が合っていたと思います。
ですが、Microsoftの公式ページ『Docs > Visual Studio Mac > コードのコンパイルとビルド > ビルド システムのカスタマイズ』では次のように解説されていました。

MSBuild とも呼ばれているこのエンジンは Microsoft によって開発され、.NET アプリケーションの構築を可能にします。 Mono フレームワークはまた、Microsoft のビルド エンジンを独自に実装しています。それが xbuild です。 ただし、現時点では xbuild は廃止となり、すべてのオペレーティング システムで MSBuild が使用されています。

(2019年9月最終更新記事)

上記のように公式が、現在時点はxbuildを廃止したとあります、、、なんですが笑
別公式ページの『Docs > Xamarin > ツール > 継続的インテグレーション > Xamarin での Jenkins の使用 > MSBuild プラグインの構成』では次のように解説されています。

...
ページの下部にある [保存] または [適用] ボタンをクリックして変更を保存すると、Jenkins を xbuild 使用してソリューションをコンパイルできるようになります。

(2017年3月最終更新記事)

ということで、良く見ると2年以上前の記事で先ほどのフェンリルさんと同じですね。どちらも記事が古いため紛らわしいと感じてしましますが、誰も悪くなくしょうがないことなので、記事を参考にするときは常に自己責任でしっかり確認して参考にしましょう。

ちなみにこの記事は、

このガイドでは、OS X を実行する専用コンピューターに Jenkins をインストールし、コンピューターの起動時に自動的に実行されるように構成する方法について説明します。

ということが冒頭で解説されており、まさしく今回の目的を実現する際に最も参考になる記事だと思います。
ですが、記事が古くなってしまい、指定されているJenkinsプラグインも状態が変わっているようなので、これまでのCI環境の作り方を知るための参考記事として読むと良さそうですね。

Xamarinのコマンドビルドについて

では、mxbuildでどうやってビルドできるのか。

iOS

error MSB4126: 指定されたソリューション構成 "Ad-Hoc|iPhone" は無効です。


$ msbuild /p:Configuration=Ad-Hoc /p:Platform=iPhone /p:ArchiveOnBuild=true /t:"Build" xgapp.sln

Mono 向け Microsoft (R) Build Engine バージョン 16.3.0-ci
Copyright (C) Microsoft Corporation.All rights reserved.

2019/12/02 19:26:34 にビルドを開始しました。
ノード 1 上のプロジェクト "/Users/gremito/Xamarin/MyApp/MyApp.sln" (Build ターゲット)。
/Users/gremito/Xamarin/MyApp/MyApp.sln.metaproj : error MSB4126: 指定されたソリューション構成 "Ad-Hoc|iPhone" は無効です。構成とプラットフォームのプロパティ (例: MSBuild.exe Solution.sln /p:Configuration=Debug /p:Platform="Any CPU") を使用して有効なソリューション構成を指定するか、または既定のソリューション構成を使用するために、それらのプロパティを空にしておいてください。 [/Users/gremito/Xamarin/MyApp/MyApp.sln]
プロジェクト "/Users/gremito/Xamarin/MyApp/MyApp.sln" (Build ターゲット) のビルドが終了しました -- 失敗。

ビルドに失敗しました。

"/Users/gremito/Xamarin/MyApp/MyApp.sln" (Build ターゲット) (1) ->
(ValidateSolutionConfiguration ターゲット) -> 
  /Users/gremito/Xamarin/MyApp/MyApp.sln.metaproj : error MSB4126: 指定されたソリューション構成 "Ad-Hoc|iPhone" は無効です。構成とプラットフォームのプロパティ (例: MSBuild.exe Solution.sln /p:Configuration=Debug /p:Platform="Any CPU") を使用して有効なソリューション構成を指定するか、または既定のソリューション構成を使用するために、それらのプロパティを空にしておいてください。 [/Users/gremito/Xamarin/MyApp/MyApp.sln]

    0 個の警告
    1 エラー

経過時間 00:00:00.37

ビルド方法

シミュレータビルドは次のコマンドラインでビルドできます。

$ msbuild /p:Platform=iPhoneSimulator /p:ArchiveOnBuild=true /t:"Build" iOS/xgapp.iOS.csproj 
Mono 向け Microsoft (R) Build Engine バージョン 16.3.0-ci
Copyright (C) Microsoft Corporation.All rights reserved.

2019/12/23 11:24:55 にビルドを開始しました。
ノード 1 上のプロジェクト "/Users/gremito/bitbucket/xgapp/iOS/xgapp.iOS.csproj" (Build ターゲット)。
_CoreCompileInterfaceDefinitions:
  Tool /Applications/Xcode.app/Contents/Developer/usr/bin/ibtool execution started with arguments: --errors --warnings --notices --output-format xml1 --minimum-deployment-target 8.0 --target-device iphone --target-device ipad --auto-activate-custom-fonts --sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk --compilation-directory /Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool /Users/gremito/bitbucket/xgapp/iOS/LaunchScreen.storyboard
  Tool /Applications/Xcode.app/Contents/Developer/usr/bin/ibtool execution started with arguments: --errors --warnings --notices --output-format xml1 --minimum-deployment-target 8.0 --target-device iphone --target-device ipad --auto-activate-custom-fonts --sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk --compilation-directory /Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool /Users/gremito/bitbucket/xgapp/iOS/Main.storyboard
  Tool /Applications/Xcode.app/Contents/Developer/usr/bin/ibtool execution started with arguments: --errors --warnings --notices --output-format xml1 --minimum-deployment-target 8.0 --target-device iphone --target-device ipad --auto-activate-custom-fonts --sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk --link /Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link /Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool/LaunchScreen.storyboardc /Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool/Main.storyboardc
    BundleResources Output:
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/Info.plist
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/8rJ-Kc-sve-view-QS5-Rx-YEW.nib/objects-13.0+.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/8rJ-Kc-sve-view-QS5-Rx-YEW.nib/runtime.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/tabViewController.nib/objects-13.0+.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/tabViewController.nib/runtime.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/UIViewController-2248.nib/objects-13.0+.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/UIViewController-2248.nib/runtime.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/2248-view-2249.nib/objects-13.0+.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/2248-view-2249.nib/runtime.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/UIViewController-118.nib/objects-13.0+.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/UIViewController-118.nib/runtime.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/75-view-76.nib/objects-13.0+.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/75-view-76.nib/runtime.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/118-view-119.nib/objects-13.0+.nib
      obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/118-view-119.nib/runtime.nib
      obj/iPhoneSimulator/Debug/ibtool-link/LaunchScreen.storyboardc/Info.plist
      obj/iPhoneSimulator/Debug/ibtool-link/LaunchScreen.storyboardc/X5k-f2-b5h-view-yd7-JS-zBw.nib/objects-13.0+.nib
      obj/iPhoneSimulator/Debug/ibtool-link/LaunchScreen.storyboardc/X5k-f2-b5h-view-yd7-JS-zBw.nib/runtime.nib
      obj/iPhoneSimulator/Debug/ibtool-link/LaunchScreen.storyboardc/UIViewController-X5k-f2-b5h.nib/objects-13.0+.nib
      obj/iPhoneSimulator/Debug/ibtool-link/LaunchScreen.storyboardc/UIViewController-X5k-f2-b5h.nib/runtime.nib
    OutputManifests Output:
      obj/iPhoneSimulator/Debug/ibtool-manifests/LaunchScreen.storyboardc
      obj/iPhoneSimulator/Debug/ibtool-manifests/Main.storyboardc
      obj/iPhoneSimulator/Debug/ibtool-manifests/link
_BeforeCoreCompileImageAssets:
  ディレクトリ "obj/iPhoneSimulator/Debug/actool" は存在しません。省略します。
_CoreCompileImageAssets:
  Tool /Applications/Xcode.app/Contents/Developer/usr/bin/actool execution started with arguments: --errors --warnings --notices --output-format xml1 --output-partial-info-plist /Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/actool/partial-info.plist --app-icon AppIcon --compress-pngs --target-device iphone --target-device ipad --minimum-deployment-target 8.0 --platform iphonesimulator --compile /Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/actool/bundle /Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/actool/cloned-assets/Assets.xcassets
/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/actool/cloned-assets/Assets.xcassets : actool warning : The app icon set "AppIcon" has an unassigned child. [/Users/gremito/bitbucket/xgapp/iOS/xgapp.iOS.csproj]
_CoreCompileColladaAssets:
入力がないため、ターゲット "_CoreCompileColladaAssets" を省略しています。
_BeforeCoreCompileSceneKitAssets:
入力がないため、ターゲット "_BeforeCoreCompileSceneKitAssets" を省略しています。
_BeforeCoreCompileSceneKitAssets:
入力がないため、ターゲット "_BeforeCoreCompileSceneKitAssets" を省略しています。
_BeforeCoreCompileSceneKitAssets:
入力がないため、ターゲット "_BeforeCoreCompileSceneKitAssets" を省略しています。
_CoreCompileSceneKitAssets:
入力がないため、ターゲット "_CoreCompileSceneKitAssets" を省略しています。
_BeforeCompileTextureAtlases:
入力がないため、ターゲット "_BeforeCompileTextureAtlases" を省略しています。
_BeforeCompileTextureAtlases:
入力がないため、ターゲット "_BeforeCompileTextureAtlases" を省略しています。
_BeforeCompileTextureAtlases:
入力がないため、ターゲット "_BeforeCompileTextureAtlases" を省略しています。
_CoreCompileTextureAtlases:
入力がないため、ターゲット "_CoreCompileTextureAtlases" を省略しています。
_BeforeCompileCoreMLModels:
  ディレクトリ "obj/iPhoneSimulator/Debug/coremlc" は存在しません。省略します。
_CoreOptimizePngImages:
出力がないため、ターゲット "_CoreOptimizePngImages" を省略しています。
_CoreOptimizePropertyLists:
入力がないため、ターゲット "_CoreOptimizePropertyLists" を省略しています。
_CoreOptimizeLocalizationFiles:
入力がないため、ターゲット "_CoreOptimizeLocalizationFiles" を省略しています。
GenerateTargetFrameworkMonikerAttribute:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "GenerateTargetFrameworkMonikerAttribute" を省略します。
CoreCompile:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "CoreCompile" を省略します。
_CopyFilesMarkedCopyLocal:
  "/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug//xgapp.iOS.csproj.CopyComplete" のタッチ タスクを実行しています。
CopyFilesToOutputDirectory:
  xgapp.iOS -> /Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.exe
IncrementalClean:
  ファイル "/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/device-builds/iphone12.1-13.2/ibtool/_BundleResourceWithLogicalName.items" を削除しています。
  ファイル "/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/device-builds/iphone12.1-13.2/actool/_PartialAppManifest.items" を削除しています。
  ファイル "/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/device-builds/iphone12.1-13.2/actool/_BundleResourceWithLogicalName.items" を削除しています。
  ファイル "/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/device-builds/iphone12.1-13.2/coremlc/_PartialAppManifest.items" を削除しています。
  ファイル "/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/device-builds/iphone12.1-13.2/coremlc/_BundleResourceWithLogicalName.items" を削除しています。
_DetectSigningIdentity:
  Detected signing identity:
    Bundle Id: net.gremito.app.xamarin.xgapp.xgapp
    App Id: net.gremito.app.xamarin.xgapp.xgapp
_CopyResourcesToBundle:
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/Info.plist' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/Info.plist'
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/8rJ-Kc-sve-view-QS5-Rx-YEW.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/8rJ-Kc-sve-view-QS5-Rx-YEW.nib/objects-13.0+.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/8rJ-Kc-sve-view-QS5-Rx-YEW.nib/objects-13.0+.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/8rJ-Kc-sve-view-QS5-Rx-YEW.nib/runtime.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/8rJ-Kc-sve-view-QS5-Rx-YEW.nib/runtime.nib'
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/tabViewController.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/tabViewController.nib/objects-13.0+.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/tabViewController.nib/objects-13.0+.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/tabViewController.nib/runtime.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/tabViewController.nib/runtime.nib'
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/UIViewController-2248.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/UIViewController-2248.nib/objects-13.0+.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/UIViewController-2248.nib/objects-13.0+.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/UIViewController-2248.nib/runtime.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/UIViewController-2248.nib/runtime.nib'
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/2248-view-2249.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/2248-view-2249.nib/objects-13.0+.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/2248-view-2249.nib/objects-13.0+.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/2248-view-2249.nib/runtime.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/2248-view-2249.nib/runtime.nib'
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/UIViewController-118.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/UIViewController-118.nib/objects-13.0+.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/UIViewController-118.nib/objects-13.0+.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/UIViewController-118.nib/runtime.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/UIViewController-118.nib/runtime.nib'
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/75-view-76.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/75-view-76.nib/objects-13.0+.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/75-view-76.nib/objects-13.0+.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/75-view-76.nib/runtime.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/75-view-76.nib/runtime.nib'
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/118-view-119.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/118-view-119.nib/objects-13.0+.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/118-view-119.nib/objects-13.0+.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/Main.storyboardc/118-view-119.nib/runtime.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Main.storyboardc/118-view-119.nib/runtime.nib'
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/LaunchScreen.storyboardc'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/LaunchScreen.storyboardc/Info.plist' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/LaunchScreen.storyboardc/Info.plist'
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/LaunchScreen.storyboardc/X5k-f2-b5h-view-yd7-JS-zBw.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/LaunchScreen.storyboardc/X5k-f2-b5h-view-yd7-JS-zBw.nib/objects-13.0+.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/LaunchScreen.storyboardc/X5k-f2-b5h-view-yd7-JS-zBw.nib/objects-13.0+.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/LaunchScreen.storyboardc/X5k-f2-b5h-view-yd7-JS-zBw.nib/runtime.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/LaunchScreen.storyboardc/X5k-f2-b5h-view-yd7-JS-zBw.nib/runtime.nib'
  Creating directory '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/LaunchScreen.storyboardc/UIViewController-X5k-f2-b5h.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/LaunchScreen.storyboardc/UIViewController-X5k-f2-b5h.nib/objects-13.0+.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/LaunchScreen.storyboardc/UIViewController-X5k-f2-b5h.nib/objects-13.0+.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/ibtool-link/LaunchScreen.storyboardc/UIViewController-X5k-f2-b5h.nib/runtime.nib' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/LaunchScreen.storyboardc/UIViewController-X5k-f2-b5h.nib/runtime.nib'
  Copying file from '/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/actool/bundle/Assets.car' to '/Users/gremito/bitbucket/xgapp/iOS/bin/iPhoneSimulator/Debug/xgapp.iOS.app/Assets.car'
_DetectDebugNetworkConfiguration:
    DebugIPAddresses: 127.0.0.1
_CompileAppManifest:
  ディレクトリ "bin/iPhoneSimulator/Debug/xgapp.iOS.app.dSYM" は存在しません。省略します。
_ParseExtraMtouchArgs:
    NoSymbolStrip Output: true
    NoDSymUtil Output: false
_CompileToNative:
  /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/bin/mtouch @/Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/response-file.rsp 
  "AlwaysCreate" が指定されたため "bin/iPhoneSimulator/Debug/mtouch.stamp" を作成しています。
_CopyAppExtensionsToBundle:
入力がないため、ターゲット "_CopyAppExtensionsToBundle" を省略しています。
_CodesignNativeLibraries:
  Tool /usr/bin/codesign execution started with arguments: -v --force --sign - --timestamp=none bin/iPhoneSimulator/Debug/xgapp.iOS.app/libmono-native.dylib
_CollectFrameworks:
  No Frameworks directory found.
プロジェクト "/Users/gremito/bitbucket/xgapp/iOS/xgapp.iOS.csproj" (Build ターゲット) のビルドが完了しました。

ビルドに成功しました。

"/Users/gremito/bitbucket/xgapp/iOS/xgapp.iOS.csproj" (Build ターゲット) (1) ->
(_CoreCompileImageAssets ターゲット) -> 
  /Users/gremito/bitbucket/xgapp/iOS/obj/iPhoneSimulator/Debug/actool/cloned-assets/Assets.xcassets : actool warning : The app icon set "AppIcon" has an unassigned child. [/Users/gremito/bitbucket/xgapp/iOS/xgapp.iOS.csproj]

    1 個の警告
    0 エラー

経過時間 00:00:05.46

Droid

error : The OutputPath property is not set for project 'MyApp.Droid.csproj'.

同様にAndroidビルドも行うと以下のようにエラーが出ました。

$ msbuild /p:Configuration=Release /p:Platform=Android /t:SignAndroidPackage Droid/xgapp.Droid.csproj

Mono 向け Microsoft (R) Build Engine バージョン 16.3.0-ci
Copyright (C) Microsoft Corporation.All rights reserved.

2019/12/02 19:24:02 にビルドを開始しました。
ノード 1 上のプロジェクト "/Users/gremito/Xamarin/MyApp/Droid/MyApp.Droid.csproj" (SignAndroidPackage ターゲット)。
/Library/Frameworks/Mono.framework/Versions/6.4.0/lib/mono/msbuild/Current/bin/Microsoft.Common.CurrentVersion.targets(791,5): error : The OutputPath property is not set for project 'MyApp.Droid.csproj'.  Please check to make sure that you have specified a valid combination of Configuration and Platform for this project.  Configuration='Release'  Platform='Android'.  You may be seeing this message because you are trying to build a project without a solution file, and have specified a non-default Configuration or Platform that doesn't exist for this project. [/Users/gremito/Xamarin/MyApp/Droid/MyApp.Droid.csproj]
プロジェクト "/Users/gremito/Xamarin/MyApp/Droid/MyApp.Droid.csproj" (SignAndroidPackage ターゲット) のビルドが終了しました -- 失敗。

ビルドに失敗しました。

"/Users/gremito/Xamarin/MyApp/Droid/MyApp.Droid.csproj" (SignAndroidPackage ターゲット) (1) ->
(_CheckForInvalidConfigurationAndPlatform ターゲット) -> 
  /Library/Frameworks/Mono.framework/Versions/6.4.0/lib/mono/msbuild/Current/bin/Microsoft.Common.CurrentVersion.targets(791,5): error : The OutputPath property is not set for project 'MyApp.Droid.csproj'.  Please check to make sure that you have specified a valid combination of Configuration and Platform for this project.  Configuration='Release'  Platform='Android'.  You may be seeing this message because you are trying to build a project without a solution file, and have specified a non-default Configuration or Platform that doesn't exist for this project. [/Users/gremito/Xamarin/MyApp/Droid/MyApp.Droid.csproj]

    0 個の警告
    1 エラー

経過時間 00:00:00.58

エラー内容を良く読むと、

error : The OutputPath property is not set for project 'MyApp.Droid.csproj'. Please check to make sure that you have specified a valid combination of Configuration and Platform for this project. Configuration='Release' Platform='Android'.

の箇所でConfigurationPlatformの組み合わせがおかしいと指摘されています。
なのでPlatformのオプションを外して再度ビルドしてみると正常にビルドが行われ、オプションを/p:Configuration=Release/p:Configuration=Debugとで行うと./bin配下に以下のようにできてあります。

スクリーンショット 2019-12-05 10.55.39.png

ビルド方法

上記のコマンド内にあるp:Platformオプションは必要なく、次のコマンドラインで.apkがビルドできます。

$ msbuild /p:Configuration=Debug /t:SignAndroidPackage ./Droid/xgapp.Droid.csproj
Mono 向け Microsoft (R) Build Engine バージョン 16.3.0-ci
Copyright (C) Microsoft Corporation.All rights reserved.

2019/12/23 11:10:16 にビルドを開始しました。
ノード 1 上のプロジェクト "/Users/gremito/bitbucket/xgapp/Droid/xgapp.Droid.csproj" (SignAndroidPackage ターゲット)。
_CleanIntermediateIfNuGetsChange:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_CleanIntermediateIfNuGetsChange" を省略します。
_ResolveSdks:
  Found Java SDK version 1.8.0.
  Found Java SDK version 1.8.0.
_ValidateAndroidPackageProperties:
    PackageName: net.gremito.app.xamarin.xgapp.xgapp
/Users/gremito/bitbucket/xgapp/Droid/Properties/AndroidManifest.xml : warning XA4216: AndroidManifest.xml //uses-sdk/@android:minSdkVersion '15' is less than API-16, this configuration is not supported. [/Users/gremito/bitbucket/xgapp/Droid/xgapp.Droid.csproj]
_ResolveXamarinAndroidTools:
   Found Xamarin.Android 10.0.6.2
_CheckInstantRunCondition:
  Dex Fast Deployment Enabled: False
_ResolveMonoAndroidSdks:
  MonoAndroid Tools: /Library/Frameworks/Xamarin.Android.framework/Libraries/xbuild/Xamarin/Android/
  Android Platform API level: 27
  TargetFrameworkVersion: v8.1
  Android NDK: /Users/gremito/Library/Android/android-ndk-r20/
  Android SDK: /Users/gremito/Library/Android/sdk/
  Android SDK Build Tools: /Users/gremito/Library/Android/sdk/build-tools/28.0.3/
  Java SDK: /Users/gremito/Library/Developer/Xamarin/jdk/microsoft_dist_openjdk_1.8.0.25/
  Application Java class: android.app.Application
_ResolveLibraryProjectImports:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_ResolveLibraryProjectImports" を省略します。
_BuildLibraryImportsCache:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_BuildLibraryImportsCache" を省略します。
_BuildAdditionalResourcesCache:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_BuildAdditionalResourcesCache" を省略します。
_CreateAdditionalResourceCache:
出力がないため、ターゲット "_CreateAdditionalResourceCache" を省略しています。
_GenerateAndroidResourceDir:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_GenerateAndroidResourceDir" を省略します。
_GenerateLayoutBindings:
出力がないため、ターゲット "_GenerateLayoutBindings" を省略しています。
_ConvertLibraryResourcesCases:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_ConvertLibraryResourcesCases" を省略します。
_CompileAndroidLibraryResources:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_CompileAndroidLibraryResources" を省略します。
_ConvertResourcesCases:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_ConvertResourcesCases" を省略します。
_CompileResources:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_CompileResources" を省略します。
VectorDrawableCheckBuildToolsVersionTask:
  Checking Android SDK Build-tools version...
  Selected Android SDK Build Tools Path: /Users/gremito/Library/Android/sdk/build-tools/28.0.3/
  Selected Android SDK Build Tools Version: 28.0.3
  Android SDK Build Tools Version: 28.0.3 meets minimum requirements for Vector Drawables. OK.
  Finished Checking Android SDK Build-tools version.
_UpdateAndroidResgen:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_UpdateAndroidResgen" を省略します。
GenerateTargetFrameworkMonikerAttribute:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "GenerateTargetFrameworkMonikerAttribute" を省略します。
CoreCompile:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "CoreCompile" を省略します。
_CopyFilesMarkedCopyLocal:
  "/Users/gremito/bitbucket/xgapp/Droid/obj/Debug//xgapp.Droid.csproj.CopyComplete" のタッチ タスクを実行しています。
CopyFilesToOutputDirectory:
  xgapp.Droid -> /Users/gremito/bitbucket/xgapp/Droid/bin/Debug/xgapp.Droid.dll
_ResolveAssemblies:
    Adding assembly reference for xgapp.Droid, recursively...
    Adding assembly reference for Java.Interop, recursively...
    Adding assembly reference for Mono.Android, recursively...
      Adding assembly reference for System.Drawing.Common, recursively...
      Adding assembly reference for System.Runtime.Serialization, recursively...
        Adding assembly reference for System.ServiceModel.Internals, recursively...
    Adding assembly reference for mscorlib, recursively...
    Adding assembly reference for Newtonsoft.Json, recursively...
      Adding assembly reference for System.IO, recursively...
      Adding assembly reference for System.Threading.Tasks, recursively...
      Adding assembly reference for System.Xml.XDocument, recursively...
        Adding assembly reference for System.Xml.Linq, recursively...
      Adding assembly reference for System.Collections, recursively...
      Adding assembly reference for System.Globalization, recursively...
      Adding assembly reference for System.Runtime.Serialization.Primitives, recursively...
      Adding assembly reference for System.Reflection, recursively...
      Adding assembly reference for System.Linq.Expressions, recursively...
      Adding assembly reference for System.Dynamic.Runtime, recursively...
      Adding assembly reference for System.Linq, recursively...
      Adding assembly reference for System.Diagnostics.Debug, recursively...
      Adding assembly reference for System.ObjectModel, recursively...
      Adding assembly reference for System.Text.RegularExpressions, recursively...
      Adding assembly reference for System.Xml.ReaderWriter, recursively...
      Adding assembly reference for System.Text.Encoding, recursively...
      Adding assembly reference for System.Runtime.Extensions, recursively...
      Adding assembly reference for System.Threading, recursively...
      Adding assembly reference for System.Reflection.Extensions, recursively...
      Adding assembly reference for System.Text.Encoding.Extensions, recursively...
      Adding assembly reference for Microsoft.CSharp, recursively...
    Adding assembly reference for Plugin.Connectivity.Abstractions, recursively...
    Adding assembly reference for Plugin.Connectivity, recursively...
    Adding assembly reference for Plugin.CurrentActivity, recursively...
    Adding assembly reference for Plugin.Share.Abstractions, recursively...
    Adding assembly reference for Plugin.Share, recursively...
    Adding assembly reference for System.Core, recursively...
    Adding assembly reference for System, recursively...
      Adding assembly reference for Mono.Security, recursively...
      Adding assembly reference for System.Numerics, recursively...
    Adding assembly reference for System.Net.Http, recursively...
    Adding assembly reference for System.Runtime, recursively...
      Adding assembly reference for System.ComponentModel.Composition, recursively...
    Adding assembly reference for System.Xml, recursively...
    Adding assembly reference for Xamarin.Android.Arch.Core.Common, recursively...
    Adding assembly reference for Xamarin.Android.Arch.Lifecycle.Common, recursively...
    Adding assembly reference for Xamarin.Android.Arch.Lifecycle.Runtime, recursively...
    Adding assembly reference for Xamarin.Android.Support.Animated.Vector.Drawable, recursively...
    Adding assembly reference for Xamarin.Android.Support.Annotations, recursively...
    Adding assembly reference for Xamarin.Android.Support.Compat, recursively...
    Adding assembly reference for Xamarin.Android.Support.Core.UI, recursively...
    Adding assembly reference for Xamarin.Android.Support.Core.Utils, recursively...
    Adding assembly reference for Xamarin.Android.Support.CustomTabs, recursively...
    Adding assembly reference for Xamarin.Android.Support.Design, recursively...
    Adding assembly reference for Xamarin.Android.Support.Fragment, recursively...
    Adding assembly reference for Xamarin.Android.Support.Media.Compat, recursively...
    Adding assembly reference for Xamarin.Android.Support.Transition, recursively...
    Adding assembly reference for Xamarin.Android.Support.v4, recursively...
    Adding assembly reference for Xamarin.Android.Support.v7.AppCompat, recursively...
    Adding assembly reference for Xamarin.Android.Support.v7.CardView, recursively...
    Adding assembly reference for Xamarin.Android.Support.v7.RecyclerView, recursively...
    Adding assembly reference for Xamarin.Android.Support.Vector.Drawable, recursively...
_CopyConfigFiles:
入力がないため、ターゲット "_CopyConfigFiles" を省略しています。
_CopyIntermediateAssemblies:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_CopyIntermediateAssemblies" を省略します。
_ConvertPdbFiles:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_ConvertPdbFiles" を省略します。
_CopyPdbFiles:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_CopyPdbFiles" を省略します。
_CopyMdbFiles:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_CopyMdbFiles" を省略します。
_LinkAssembliesNoShrink:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_LinkAssembliesNoShrink" を省略します。
_GenerateJavaStubs:
  "obj/Debug/stamp/_GenerateJavaStubs.stamp" のタッチ タスクを実行しています。
_ConvertCustomView:
  "obj/Debug/stamp/_ConvertCustomView.stamp" のタッチ タスクを実行しています。
_AddStaticResources:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_AddStaticResources" を省略します。
_GeneratePackageManagerJava:
  "obj/Debug/stamp/_GeneratePackageManagerJava.stamp" のタッチ タスクを実行しています。
_CreateAdditionalResourceCache:
出力がないため、ターゲット "_CreateAdditionalResourceCache" を省略しています。
_GenerateAndroidAssetsDir:
出力がないため、ターゲット "_GenerateAndroidAssetsDir" を省略しています。
_CreateBaseApk:
  "obj/Debug/android/bin/packaged_resources" のタッチ タスクを実行しています。
_CreateAdditionalResourceCache:
出力がないため、ターゲット "_CreateAdditionalResourceCache" を省略しています。
_CompileJava:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_CompileJava" を省略します。
_CreateAdditionalResourceCache:
出力がないため、ターゲット "_CreateAdditionalResourceCache" を省略しています。
_CompileToDalvikWithDx:
すべての出力ファイルが入力ファイルに対して最新なので、ターゲット "_CompileToDalvikWithDx" を省略します。
/Library/Frameworks/Mono.framework/External/xbuild/Xamarin/Android/Xamarin.Android.Common.targets(588,3): warning XA1008: The TargetFrameworkVersion (27) must not be lower than targetSdkVersion (28). You should either, increase the `$(TargetFrameworkVersion)` of your project. Or decrease the `android:targetSdkVersion` in your `AndroidManifest.xml` to correct this issue. [/Users/gremito/bitbucket/xgapp/Droid/xgapp.Droid.csproj]
_CopyPackage:
  ファイル "bin/Debug/net.gremito.app.xamarin.xgapp.xgapp.apk" を削除しています。
  "/Users/gremito/bitbucket/xgapp/Droid/obj/Debug/android/bin/net.gremito.app.xamarin.xgapp.xgapp.apk" から "/Users/gremito/bitbucket/xgapp/Droid/bin/Debug/net.gremito.app.xamarin.xgapp.xgapp.apk" へファイルをコピーしています。
_ResolveAndroidSigningKey:
  "obj/Debug/android_debug_keystore.flag" のタッチ タスクを実行しています。
_Sign:
  /Users/gremito/Library/Developer/Xamarin/jdk/microsoft_dist_openjdk_1.8.0.25/bin/keytool -list -alias androiddebugkey -storepass android -keypass android -keystore "/Users/gremito/.local/share/Xamarin/Mono for Android/debug.keystore" 
  ファイル "/Users/gremito/bitbucket/xgapp/Droid/bin/Debug/net.gremito.app.xamarin.xgapp.xgapp-Signed.apk" を削除しています。
  /Users/gremito/Library/Android/sdk/build-tools/28.0.3/zipalign -p 4 "/Users/gremito/bitbucket/xgapp/Droid/obj/Debug/android/bin/net.gremito.app.xamarin.xgapp.xgapp.apk" "bin/Debug//net.gremito.app.xamarin.xgapp.xgapp-Signed.apk" 
  /Users/gremito/Library/Developer/Xamarin/jdk/microsoft_dist_openjdk_1.8.0.25/bin/java -jar /Users/gremito/Library/Android/sdk/build-tools/28.0.3/lib/apksigner.jar sign --ks "/Users/gremito/.local/share/Xamarin/Mono for Android/debug.keystore" --ks-pass pass:android --ks-key-alias androiddebugkey --key-pass pass:android --min-sdk-version 15 --max-sdk-version 28  /Users/gremito/bitbucket/xgapp/Droid/bin/Debug/net.gremito.app.xamarin.xgapp.xgapp-Signed.apk 
  Signed android package 'bin/Debug/net.gremito.app.xamarin.xgapp.xgapp-Signed.apk'
プロジェクト "/Users/gremito/bitbucket/xgapp/Droid/xgapp.Droid.csproj" (SignAndroidPackage ターゲット) のビルドが完了しました。

ビルドに成功しました。

"/Users/gremito/bitbucket/xgapp/Droid/xgapp.Droid.csproj" (SignAndroidPackage ターゲット) (1) ->
(_ValidateAndroidPackageProperties ターゲット) -> 
  /Users/gremito/bitbucket/xgapp/Droid/Properties/AndroidManifest.xml : warning XA4216: AndroidManifest.xml //uses-sdk/@android:minSdkVersion '15' is less than API-16, this configuration is not supported. [/Users/gremito/bitbucket/xgapp/Droid/xgapp.Droid.csproj]


"/Users/gremito/bitbucket/xgapp/Droid/xgapp.Droid.csproj" (SignAndroidPackage ターゲット) (1) ->
(_CheckGoogleSdkRequirements ターゲット) -> 
  /Library/Frameworks/Mono.framework/External/xbuild/Xamarin/Android/Xamarin.Android.Common.targets(588,3): warning XA1008: The TargetFrameworkVersion (27) must not be lower than targetSdkVersion (28). You should either, increase the `$(TargetFrameworkVersion)` of your project. Or decrease the `android:targetSdkVersion` in your `AndroidManifest.xml` to correct this issue. [/Users/gremito/bitbucket/xgapp/Droid/xgapp.Droid.csproj]

    2 個の警告
    0 エラー

経過時間 00:00:07.38

申請用ビルド

公式ドキュメントの[Initial support for Android App Bundle publishing format](Initial support for Android App Bundle publishing format)が参考になりました。

$ msbuild /t:SignAndroidPackage /p:Configuration=Release /p:AndroidKeyStore=True /p:AndroidSigningKeyStore=/Users/gremito/Documents/****.jks /p:AndroidSigningStorePass=**** /p:AndroidSigningKeyAlias=**** /p:AndroidSigningKeyPass=**** ./Droid/xgapp.Droid.csproj

成果物のアプリをエミュレータにインストールして起動させるまで

前回の『スマホアプリのコマンドビルドまとめ(iOS編)』でiOSシミュレータでのアプリインストールと起動までのCLIコマンドをまとめましたので、今回はAndroidエミュレータでの手順をまとめます。

全く触ったことがない方は、公式にadbやエミュレータの詳細があるので確認しておきましょう。

#`Android Studio`から`AVD Manager`を開きエミュレータを作っておく。
#`adb`コマンドおよび`emulator`コマンドが扱えるように設定する。
$vi ~/.bash_profile

...

# Android
export ANDROID_HOME=/Users/gremito/Library/Android/sdk
export PATH=$ANDROID_HOME/emulator:$PATH
export PATH=$ANDROID_HOME/tools:$PATH
export PATH=$ANDROID_HOME/tools/bin:$PATH
export PATH=$ANDROID_HOME/platform-tools:$PATH

...

$source ~/.bash_profile

#`$emulator -list-avds`で作っておいたエミュレータが表示されることを確認する。
$ emulator -list-avds
Pixel_3a_API_28

#`$emulator -avd 【エミュレータ名】`でエミュレータを起動を確認する。
# 起動後は [cnt^ & c] で終了させられます。
$ emulator -avd Pixel_3a_API_28

# アプリをエミュレータにインストール(.apkを指定)
# ※エミュレータやUSB接続などで複数のデバイス接続を行なっている場合は`-s`オプションで特定のデバイスを指定する必要があります。
$ adb install xxxxxxxxxx.apk

# アプリを起動する
adb shell am start \
  -a android.intent.action.VIEW \
  -n 【パッケージ名】/【MainActivityのパス】

# エミュレータからアプリをアンインストール(パッケージ名を指定)
$ adb uninstall net.gremito.xamarin.app

iOSの$idb boot ...でシミュレータを起動してもその後もターミナル上の操作は可能です。
ですが、上記のように$emulator -avd ...ではターミナル上の操作ができなくなるので、CIにE2Eテストの環境を作って自動テストを用意する際は、Androidエミュレータは起動しっぱなし状態にする必要がありますね。

参考記事

 

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?