Compare commits

...

46 Commits
v0.1 ... v0.3

Author SHA1 Message Date
Webber
948a53575b Remove console statements 2019-12-22 22:43:47 +01:00
Webber
5abc3164f9 Update documentation 2019-12-22 22:43:47 +01:00
Webber
3ae2ac1bb9 Remove failing examples 2019-12-22 22:43:47 +01:00
Webber
250a665fce Make version pattern slightly less strict 2019-12-22 22:43:47 +01:00
Webber
7c90cddd43 Cleanup 2019-12-22 22:43:47 +01:00
Webber
ed7918e4f4 remove unnecessary tests 2019-12-22 22:43:47 +01:00
Webber
b4a1f1ab7b Cleanup targetPlatformMap 2019-12-22 22:43:47 +01:00
Webber
bafc8e806b Allow builds for all targets 2019-12-22 22:43:47 +01:00
Webber
2ab738c083 Run docker from javascript 2019-12-22 22:43:47 +01:00
Webber
9a639e97e3 Test not working defaults 2019-12-22 22:43:47 +01:00
Webber
0130f1d603 Fix tag 2019-12-22 22:43:47 +01:00
Webber
454ff6054c Set build args for container 2019-12-22 22:43:47 +01:00
Webber
dccdec9d3b Bootstrap docker from js 2019-12-22 22:43:47 +01:00
Webber
0bb0dbd7be Package to dist folder using @zeit/ncc 2019-12-22 22:43:47 +01:00
Webber
4312758325 add jest 2019-12-22 22:43:47 +01:00
Webber
302bd4c880 Add initial script 2019-12-22 22:43:47 +01:00
Webber
25294aec32 Add test for javascript 2019-12-22 22:43:47 +01:00
Webber
ae18dc32c9 Add quality tools for javascript 2019-12-22 22:43:47 +01:00
Webber
69c4feba8f Add javascript platform 2019-12-22 22:43:47 +01:00
Webber
119a2b3b02 Skip test job for now 2019-12-22 22:43:47 +01:00
Webber
b385278dec Remove plural builds 2019-12-22 22:43:47 +01:00
Webber
9996187855 Upload artifacts from each job 2019-12-22 22:43:47 +01:00
Webber
093eb69424 Remove unnecessary activate job 2019-12-22 22:43:47 +01:00
Webber
26f0080aae Use activate and license step in relevant jobs only 2019-12-22 22:43:47 +01:00
Webber
f7727333bf Add tests to test-project 2019-12-22 22:43:47 +01:00
Webber
f14fe9806a Remove checkout as prerequisite 2019-12-22 22:43:47 +01:00
Webber
c53fa402db Add checkout step in relevant jobs only 2019-12-22 22:43:47 +01:00
Webber
b955df1dcb Run test runner at master 2019-12-22 22:43:47 +01:00
Webber
9a21280b09 Change workflow and variables to allow multiple build jobs 2019-12-22 22:43:47 +01:00
Webber
e9496f5ba2 Test variable action versions 2019-12-22 22:43:47 +01:00
Webber
75aac428f1 Rename BUILD_COMMAND to BUILD_METHOD 2019-12-08 02:02:32 +01:00
Webber
564f7e60fd Use latest return license action 2019-12-08 02:02:32 +01:00
Webber
75fc07ef3c Update readme, prepare for v0.2 2019-12-08 02:02:32 +01:00
Webber
23f2c2c0fa remove dead files 2019-12-08 02:02:32 +01:00
Webber
bed2573f99 Simplify BUILD_METHOD selection, fix derp 2019-12-08 00:48:22 +01:00
Webber
bda25a9cc1 Bring back meta file for builder 2019-12-08 00:48:22 +01:00
Webber
c8b1b80829 Include default-build-script in docker 2019-12-08 00:48:22 +01:00
Webber
35e88ac302 Rename Builder to default-build-script 2019-12-08 00:48:22 +01:00
Webber
abf39fb044 Rider-project for Builder folder 2019-12-08 00:48:22 +01:00
Webber
5d0c1ba5c1 Move Builder folder to root of the action project 2019-12-08 00:48:22 +01:00
Webber
fb7bd5bff5 Move Builder folder inside the Editor folder 2019-12-07 17:03:13 +01:00
Webber
4fc0f7dd2c Add pull requests as trigger for workflow 2019-12-07 17:03:13 +01:00
Webber
a017ab7c43 Move builder to its own folder 2019-12-07 17:03:13 +01:00
Webber
0558bc6213 Cleanup unneeded license code 2019-12-07 17:03:13 +01:00
Webber
14e59b6099 Update readme contributions and license 2019-12-03 23:42:27 +01:00
Webber
273a12eefa update readme header 2019-12-03 23:42:27 +01:00
36 changed files with 6924 additions and 276 deletions

View File

@@ -4,3 +4,4 @@
# Files required for the action
!entrypoint.sh
!action.yml
!default-build-script

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 100
tab_width = 2
trim_trailing_whitespace = true
[*.md]
max_line_length = 0
trim_trailing_whitespace = false
[COMMIT_EDITMSG]
max_line_length = 0

2
.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
**/node_modules/**
**/dist/**

21
.eslintrc.json Normal file
View File

@@ -0,0 +1,21 @@
{
"parser": "babel-eslint",
"env": {
"node": true,
"es6": true,
"jest": true
},
"parserOptions": {
"ecmaVersion": 2020,
"ecmaFeatures": {
"impliedStrict": true
}
},
"extends": ["airbnb", "plugin:unicorn/recommended", "prettier"],
"plugins": ["react", "jsx-a11y", "import", "prettier", "flowtype", "unicorn"],
"settings": { "react": { "version": "latest" } },
"rules": {
"prettier/prettier": "error",
"import/no-extraneous-dependencies": 0
}
}

View File

@@ -1,30 +1,54 @@
name: Actions 😎
on: [push]
on:
pull_request: {}
push: { branches: [master] }
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
jobs:
buildForWebGL:
name: Build for WebGL 🕸
buildForAllPlatforms:
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
projectPath:
- test-project
unityVersion:
- 2019.2.11f1
# - 2019.3.0f1 # requires unity-activate to upgrade
targetPlatform:
- WebGL
- iOS
# - StandaloneOSX
# - StandaloneWindows
# - StandaloneWindows64
# - StandaloneLinux64
# - PS4
# - XboxOne
# - Switch
# - Android
# - tvOS
# - Lumin
# - BJM
# - Stadia
# - WSAPlayer
# - Facebook
# - NoTarget
steps:
# Checkout repository (required to test local actions)
- name: Checkout repository
uses: actions/checkout@v1
# Configure builder
- name: Build project
id: buildStep
uses: ./
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_PROJECT_PATH: test-project
BUILD_NAME: TestBuild
BUILD_TARGET: WebGL
BUILDS_PATH: builds
BUILD_COMMAND: ""
# Upload distributables
- name: Upload Build
uses: actions/upload-artifact@v1
- uses: actions/checkout@v1
- uses: webbertakken/unity-activate@v1
- uses: ./
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
- uses: webbertakken/unity-return-license@v1
if: always()
- uses: actions/upload-artifact@v1
with:
name: Build
path: ${{ steps.buildStep.outputs.allBuildsPath }}
path: build

15
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Test Action
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 12.x
- run: yarn
- run: yarn lint
- run: yarn test

1
.gitignore vendored
View File

@@ -1 +1,2 @@
.idea
node_modules

2
.prettierignore Normal file
View File

@@ -0,0 +1,2 @@
**/node_modules/**
**/dist/**

6
.prettierrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100
}

3
.yarnrc Normal file
View File

@@ -0,0 +1,3 @@
save-prefix ""
--install.audit true
--add.audit true

View File

@@ -1,4 +1,5 @@
FROM gableroux/unity3d:2019.2.11f1-webgl
ARG IMAGE
FROM $IMAGE
LABEL "com.github.actions.name"="Unity - Builder"
LABEL "com.github.actions.description"="Build Unity projects for different platforms."
@@ -9,6 +10,7 @@ LABEL "repository"="http://github.com/webbertakken/unity-actions"
LABEL "homepage"="http://github.com/webbertakken/unity-actions"
LABEL "maintainer"="Webber Takken <webber@takken.io>"
ADD default-build-script /UnityBuilderAction
ADD entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

155
README.md
View File

@@ -1,18 +1,31 @@
# Unity - Builder
[![Actions status](https://github.com/webbertakken/unity-builder/workflows/Actions%20%F0%9F%98%8E/badge.svg)](https://github.com/webbertakken/unity-builder/actions?query=branch%3Amaster+workflow%3A%22Actions+%F0%9F%98%8E%22)
---
GitHub Action to
[build Unity projects](https://github.com/marketplace/actions/unity-builder)
for different platforms.
Part of the
[Unity Actions](https://github.com/webbertakken/unity-actions)
collection.
---
[Github Action](https://github.com/features/actions)
to build Unity projects for different platforms.
It is recommended to run the
[Test](https://github.com/webbertakken/unity-actions#test)
action from the
[Unity Actions](https://github.com/webbertakken/unity-actions)
action from the
[Unity Actions](https://github.com/webbertakken/unity-actions)
collection before running this action. This action also requires the [Activation](https://github.com/marketplace/actions/unity-activate) step.
## Documentation
See the
See the
[Unity Actions](https://github.com/webbertakken/unity-actions)
collection repository for workflow documentation and reference implementation.
@@ -24,53 +37,109 @@ Create or edit the file called `.github/workflows/main.yml` and add a job to it.
name: Build project
on: [push]
jobs:
buildForWebGL:
name: Build for WebGL 🕸
buildForSomePlatforms:
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
projectPath:
- path/to/your/project
unityVersion:
- 2019.2.11f1
targetPlatform:
- WebGL
- iOS
steps:
```
Configure the builder as follows:
```yaml
# Configure builder
- name: Build project
id: buildStep
uses: webbertakken/unity-builder@v0.1 # WIP (only webgl for now)
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
# Optional: Path to your project, leave blank for "./"
UNITY_PROJECT_PATH: path/to/your/project
# Name for your build
BUILD_NAME: TestBuild
# Optional: Builds path, leave blank for "build"
BUILDS_PATH: build
# Target platform for your build
BUILD_TARGET: WebGL
# Optional: <StaticBuildClass.StaticMethod>, defaults to Builder.BuildProject
BUILD_COMMAND: ""
```
You use the id to **upload your built files** like so:
```yaml
# Upload distributables
- name: Upload Build
uses: actions/upload-artifact@v1
- uses: actions/checkout@v1
- uses: webbertakken/unity-activate@v1
- uses: webbertakken/unity-builder@v0.3
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
- uses: webbertakken/unity-return-license@v1
if: always()
- uses: actions/upload-artifact@v1
with:
name: Build
path: ${{ steps.buildStep.outputs.allBuildsPath }}
path: build
```
Commit and push your workflow definition.
> **Notes:**
>
> - Don't forget to replace _&lt;test-project&gt;_ with your project name.
> - By default the enabled scenes from the project's settings will be built.
## Configuration options
Below options can be specified under `with:` for the `unity-builder` action.
#### projectPath
Specify the path to your Unity project to be built.
The path should be relative to the root of your project.
_**required:** `false`_
_**default:** `<your project root>`_
#### unityVersion
Version of Unity to use for building the project.
_**required:** `false`_
_**default:** `2019.2.1f11`_
#### targetPlatform
Platform that the build should target.
_**required:** `true`_
#### buildName
Name of the build.
_**required:** `false`_
_**default:** `testBuild`_
#### buildsPath
Path where the builds should be stored.
In this folder a folder will be created for every targetPlatform.
_**required:** `false`_
_**default:** `build`_
#### buildCommand
Custom command to run your build.
There are two conditions for a custom buildCommand:
- Must reference a valid path to a `static` method.
- The class must reside in the `Assets/Editor` directory.
_**example:**_
```yaml
- uses: webbertakken/unity-builder@master
with:
buildCommand: EditorNamespace.BuilderClassName.StaticBulidMethod
```
_**required:** `false`_
_**default:** Built-in script that will run a build out of the box._
## More actions
Visit
[Unity Actions](https://github.com/webbertakken/unity-actions)
Visit
[Unity Actions](https://github.com/webbertakken/unity-actions)
to find related actions for Unity.
Feel free to contribute.
## Licence
[MIT](./LICENSE)

View File

@@ -1,15 +1,37 @@
name: 'Unity - Builder'
author: Webber Takken <webber@takken.io>
description: 'Build Unity projects for different platforms.'
inputs: {}
inputs:
unityVersion:
required: false
default: ''
description: 'Version of unity to use for building the project.'
targetPlatform:
required: false
default: ''
description: 'Platform that the build should target.'
projectPath:
required: false
default: ''
description: 'Relative path to the project to be built.'
buildName:
required: false
default: ''
description: 'Name of the build.'
buildsPath:
required: false
default: ''
description: 'Path where the builds should be stored.'
buildMethod:
required: false
default: ''
description: 'Path to a Namespace.Class.StaticMethod to run to perform the build.'
outputs:
buildPath:
description: "Path where the current platform has been built to."
allBuildsPath:
description: "Path where the build folders are stored. Each platform creates a folder within it."
runs:
using: 'docker'
image: 'Dockerfile'
description: 'Path where the current platform has been built to.'
branding:
icon: 'box'
color: 'gray-dark'
runs:
using: 'node12'
main: 'dist/index.js'

15
babel.config.js Normal file
View File

@@ -0,0 +1,15 @@
const esModules = ['lodash-es'].join('|');
module.exports = {
ignore: [`/node_modules/(?!${esModules})`],
presets: [
[
'@babel/preset-env',
{
targets: {
node: true,
},
},
],
],
};

56
default-build-script/.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
#
# Note: Non default ignore file, as this only tests Builder script.
#
[Ll]ibrary/
[Tt]emp/
[Oo]bj/
[Bb]uild/
[Bb]uilds/
[Ll]ogs/
# Additional ignores
[Bb]in/
# Uncomment this line if you wish to ignore the asset store tools plugin
# [Aa]ssets/AssetStoreTools*
# IDEs
.vs/
.idea/
# Gradle cache directory
.gradle/
# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
#*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db
# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
*.mdb.meta
# Unity3D generated file on crash reports
sysinfo.txt
# Builds
*.apk
*.unitypackage
# Crashlytics generated file
crashlytics-build.properties

View File

@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;
namespace UnityBuilderAction
{
static class Builder
{
private static string EOL = Environment.NewLine;
private static void ParseCommandLineArguments(out Dictionary<string, string> providedArguments)
{
providedArguments = new Dictionary<string, string>();
string[] args = Environment.GetCommandLineArgs();
Console.WriteLine(
$"{EOL}" +
$"###########################{EOL}" +
$"# Parsing settings #{EOL}" +
$"###########################{EOL}" +
$"{EOL}"
);
// Extract flags with optional values
for (int current = 0, next = 1; current < args.Length; current++, next++) {
// Parse flag
bool isFlag = args[current].StartsWith("-");
if (!isFlag) continue;
string flag = args[current].TrimStart('-');
// Parse optional value
bool flagHasValue = next < args.Length && !args[next].StartsWith("-");
string value = flagHasValue ? args[next].TrimStart('-') : "";
// Assign
Console.WriteLine($"Found flag \"{flag}\" with value \"{value}\".");
providedArguments.Add(flag, value);
}
}
private static Dictionary<string, string> GetValidatedOptions()
{
ParseCommandLineArguments(out var validatedOptions);
if (!validatedOptions.TryGetValue("projectPath", out var projectPath)) {
Console.WriteLine("Missing argument -projectPath");
EditorApplication.Exit(110);
}
if (!validatedOptions.TryGetValue("buildTarget", out var buildTarget)) {
Console.WriteLine("Missing argument -buildTarget");
EditorApplication.Exit(120);
}
if (!Enum.IsDefined(typeof(BuildTarget), buildTarget)) {
EditorApplication.Exit(121);
}
if (!validatedOptions.TryGetValue("customBuildPath", out var customBuildPath)) {
Console.WriteLine("Missing argument -customBuildPath");
EditorApplication.Exit(130);
}
string defaultCustomBuildName = "TestBuild";
if (!validatedOptions.TryGetValue("customBuildName", out var customBuildName)) {
Console.WriteLine($"Missing argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName);
}
else if (customBuildName == "") {
Console.WriteLine($"Invalid argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName);
}
return validatedOptions;
}
public static void BuildProject()
{
// Gather values from args
var options = GetValidatedOptions();
// Gather values from project
var scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(s => s.path).ToArray();
// Define BuildPlayer Options
var buildOptions = new BuildPlayerOptions {
scenes = scenes,
locationPathName = options["customBuildPath"],
target = (BuildTarget) Enum.Parse(typeof(BuildTarget), options["buildTarget"]),
};
// Perform build
BuildReport buildReport = BuildPipeline.BuildPlayer(buildOptions);
// Summary
BuildSummary summary = buildReport.summary;
ReportSummary(summary);
// Result
BuildResult result = summary.result;
ExitWithResult(result);
}
private static void ReportSummary(BuildSummary summary)
{
Console.WriteLine(
$"{EOL}" +
$"###########################{EOL}" +
$"# Build results #{EOL}" +
$"###########################{EOL}" +
$"{EOL}" +
$"Duration: {summary.totalTime.ToString()}{EOL}" +
$"Warnings: {summary.totalWarnings.ToString()}{EOL}" +
$"Errors: {summary.totalErrors.ToString()}{EOL}" +
$"Size: {summary.totalSize.ToString()} bytes{EOL}" +
$"{EOL}"
);
}
private static void ExitWithResult(BuildResult result)
{
if (result == BuildResult.Succeeded) {
Console.WriteLine("Build succeeded!");
EditorApplication.Exit(0);
}
if (result == BuildResult.Failed) {
Console.WriteLine("Build failed!");
EditorApplication.Exit(101);
}
if (result == BuildResult.Cancelled) {
Console.WriteLine("Build cancelled!");
EditorApplication.Exit(102);
}
if (result == BuildResult.Unknown) {
Console.WriteLine("Build result is unknown!");
EditorApplication.Exit(103);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dc057061ce9f406aa6b57a62d67fe9c0
timeCreated: 1575145310

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("UnityBuilderAction")]
[assembly: AssemblyDescription("Builder script for Unity Builder action")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("UnityBuilderAction")]
[assembly: AssemblyCopyright("Copyright © 2019-present")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("031F5EE1-35CE-4F77-975A-7E326F898185")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{031F5EE1-35CE-4F77-975A-7E326F898185}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>UnityBuilderAction</RootNamespace>
<AssemblyName>UnityBuilderAction</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml" />
<Reference Include="UnityEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\..\..\Program Files\Unity\2019.2.11f1\Editor\Data\Managed\UnityEditor.dll</HintPath>
</Reference>
<Reference Include="UnityEngine">
<HintPath>C:\Program Files\Unity\2019.2.11f1\Editor\Data\Managed\UnityEngine.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Builder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

1
dist/index.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,36 +1,24 @@
#!/usr/bin/env bash
#
# Set license file path
#
LICENSE_FILE_PATH=UnityLicenseFile.ulf
#
# Set project path
#
UNITY_PROJECT_PATH=$GITHUB_WORKSPACE/$UNITY_PROJECT_PATH
UNITY_PROJECT_PATH=$GITHUB_WORKSPACE/$PROJECT_PATH
echo "Using project path \"$UNITY_PROJECT_PATH\"."
#
# Set the name for the build
#
if [ -z "$BUILD_NAME" ]; then
BUILD_NAME=buildName
BUILD_NAME="build-$(date '+%F-%H%M')"
fi
echo "Using build name \"$BUILD_NAME\"."
#
# Set the builds target platform;
#
# possible options are:
#
# Standalone, Win, Win64, OSXUniversal,
# Linux64, iOS, Android, WebGL, XboxOne,
# PS4, WindowsStoreApps, Switch, tvOS
#
# New options array:
#
# Web: WebGL
# Desktop: StandaloneOSX, StandaloneWindows, StandaloneWindows64, StandaloneLinux64
# Console: PS4, XboxOne, Switch
@@ -43,6 +31,7 @@ fi
if [ -z "$BUILD_TARGET" ]; then
BUILD_TARGET=WebGL
fi
echo "Using build target \"$BUILD_TARGET\"."
#
# Set builds path
@@ -52,57 +41,48 @@ if [ -z "$BUILDS_PATH" ]; then
BUILDS_PATH=build
fi
BUILDS_FULL_PATH=$GITHUB_WORKSPACE/$BUILDS_PATH
#
# Set path for current build (relative and full)
#
CURRENT_BUILD_PATH=$BUILDS_PATH/$BUILD_TARGET
CURRENT_BUILD_FULL_PATH=$BUILDS_FULL_PATH/$BUILD_TARGET
echo "Using build path \"$CURRENT_BUILD_PATH\"."
#
# Set the build command, must reference one of:
# Set the build method, must reference one of:
#
# - <NamespaceName.ClassName.MethodName>
# - <ClassName.MethodName>
#
# For example: `BuildCommand.PerformBuild`
#
# The method must be defined static
# The method must be declared static and placed in project/Assets/Editor
#
if [ -z "$BUILD_COMMAND" ]; then
# TODO - copy Builder class from root
EXECUTE_CUSTOM_METHOD="-executeMethod Builder.BuildProject"
if [ -z "$BUILD_METHOD" ]; then
# User has not provided their own build command.
#
# Use the script from this action which builds the scenes that are enabled in
# the project.
#
echo "Using built-in build method."
# Create Editor directory if it does not exist
mkdir -p $UNITY_PROJECT_PATH/Assets/Editor/
# Copy the build script of Unity Builder action
cp -r /UnityBuilderAction $UNITY_PROJECT_PATH/Assets/Editor/
# Set the Build method to that of UnityBuilder Action
BUILD_METHOD="UnityBuilderAction.Builder.BuildProject"
# Verify recursive paths
ls -Ralph $UNITY_PROJECT_PATH/Assets/Editor/
#
else
EXECUTE_CUSTOM_METHOD="-executeMethod $BUILD_COMMAND"
# User has provided their own build method.
# Assume they also bring their own script.
#
echo "User set build method to $BUILD_METHOD."
#
fi
#
# Copy license file from Github variables
#
# Set build method to execute as flag + argument
EXECUTE_BUILD_METHOD="-executeMethod $BUILD_METHOD"
echo "$UNITY_LICENSE" | tr -d '\r' > $LICENSE_FILE_PATH
echo "$UNITY_LICENSE" | tr -d '\r' > /root/.local/share/unity3d/Unity/Unity_lic.ulf
# TODO - test if this line has any effect
echo "$UNITY_LICENSE" | tr -d '\r' > /root/.local/share/unity3d/Unity/Unity_v2019.x.ulf
#
# Activate license
#
echo "Requesting activation"
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
/opt/Unity/Editor/Unity \
-batchmode \
-nographics \
-logFile /dev/stdout \
-quit \
-manualLicenseFile $LICENSE_FILE_PATH
# This is expected to always exit with code 1 (both success and failure).
# Convert to exit code 0 by echoing the current exit code.
echo $?
# Exit code is now 0
# The build specification below may require Unity 2019.2.11f1 or later (not tested below).
# Reference: https://docs.unity3d.com/2019.3/Documentation/Manual/CommandLineArguments.html
@@ -155,7 +135,7 @@ xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
-buildTarget "$BUILD_TARGET" \
-customBuildTarget "$BUILD_TARGET" \
-customBuildPath "$CURRENT_BUILD_FULL_PATH" \
$EXECUTE_CUSTOM_METHOD
$EXECUTE_BUILD_METHOD
# Catch exit code
BUILD_EXIT_CODE=$?

8
jest.config.js Normal file
View File

@@ -0,0 +1,8 @@
const esModules = ['lodash-es'].join('|');
module.exports = {
testEnvironment: 'node',
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
transform: { '^.+\\.(js|jsx)?$': 'babel-jest' },
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
};

57
package.json Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "unity-builder",
"version": "0.2.0",
"description": "Build Unity projects for different platforms.",
"main": "src/index.js",
"repository": "git@github.com:webbertakken/unity-builder.git",
"author": "Webber <webber@takken.io>",
"license": "MIT",
"scripts": {
"build": "ncc build --out dist --minify",
"lint": "prettier --check \"src/**/*.js\" && eslint src",
"test": "jest"
},
"dependencies": {
"@actions/core": "^1.2.0",
"@actions/exec": "1.0.2",
"@actions/github": "^2.0.0"
},
"devDependencies": {
"@babel/cli": "7.7.5",
"@babel/core": "7.7.5",
"@babel/preset-env": "7.7.7",
"@zeit/ncc": "0.20.5",
"babel-eslint": "10.0.3",
"eslint": "6.7.2",
"eslint-config-airbnb": "18.0.1",
"eslint-config-prettier": "6.7.0",
"eslint-plugin-flowtype": "4.5.2",
"eslint-plugin-import": "2.19.1",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-prettier": "3.1.2",
"eslint-plugin-react": "7.17.0",
"eslint-plugin-unicorn": "14.0.1",
"husky": "4.0.0-beta.5",
"jest": "24.9.0",
"lint-staged": "9.5.0",
"lodash-es": "4.17.15",
"prettier": "1.19.1"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged && yarn build && git add dist"
}
},
"lint-staged": {
"*.{js,jsx}": [
"prettier --write",
"eslint",
"git add",
"jest --findRelatedTests"
],
"*.{json,md,yaml,yml}": [
"prettier --write",
"git add"
]
}
}

22
src/index.js Normal file
View File

@@ -0,0 +1,22 @@
import Action from './model/action';
import Docker from './model/docker';
import ImageTag from './model/image-tag';
import Input from './model/input';
const core = require('@actions/core');
async function action() {
Action.checkCompatibility();
const { dockerfile, workspace } = Action;
const { version, platform, projectPath, buildName, buildsPath, method } = Input.getFromUser();
const baseImage = new ImageTag({ version, platform });
const builtImage = await Docker.build({ path: workspace, dockerfile, baseImage });
await Docker.run(builtImage, { workspace, platform, projectPath, buildName, buildsPath, method });
}
action().catch(error => {
core.setFailed(error.message);
});

47
src/model/action.js Normal file
View File

@@ -0,0 +1,47 @@
import path from 'path';
export default class Action {
static get supportedPlatforms() {
return ['linux'];
}
static get isRunningLocally() {
return process.env.RUNNER_WORKSPACE === undefined;
}
static get isRunningFromSource() {
return __dirname !== 'dist';
}
static get name() {
return 'unity-builder';
}
static get rootFolder() {
if (!Action.isRunningLocally) {
const workspace = process.env.RUNNER_WORKSPACE;
return `${workspace}/${path.basename(workspace)}`;
}
if (Action.isRunningFromSource) {
return path.dirname(path.dirname(path.dirname(__filename)));
}
return path.dirname(path.dirname(__filename));
}
static get dockerfile() {
return `${Action.rootFolder}/Dockerfile`;
}
static get workspace() {
return process.env.GITHUB_WORKSPACE;
}
static checkCompatibility() {
const currentPlatform = process.platform;
if (!Action.supportedPlatforms.includes(currentPlatform)) {
throw new Error(`Currently ${currentPlatform}-platform is not supported`);
}
}
}

20
src/model/action.test.js Normal file
View File

@@ -0,0 +1,20 @@
import path from 'path';
import Action from './action';
describe('Action', () => {
describe('compatibility check', () => {
it('throws for anything other than linux', () => {
if (process.platform !== 'linux') {
expect(() => Action.checkCompatibility()).toThrow();
} else {
expect(() => Action.checkCompatibility()).not.toThrow();
}
});
});
it('returns the root folder of the action', () => {
const { rootFolder, name } = Action;
expect(path.basename(rootFolder)).toStrictEqual(name);
});
});

55
src/model/docker.js Normal file
View File

@@ -0,0 +1,55 @@
import { exec } from '@actions/exec';
import ImageTag from './image-tag';
export default class Docker {
static async build(buildParameters, silent = false) {
const { path, dockerfile, baseImage } = buildParameters;
const { version, platform } = baseImage;
const tag = new ImageTag({ repository: '', name: 'unity-builder', version, platform });
const command = `docker build ${path} \
--file ${dockerfile} \
--build-arg IMAGE=${baseImage} \
--tag ${tag}`;
await exec(command, null, { silent });
return tag;
}
static async run(image, parameters, silent = false) {
const { workspace, platform, projectPath, buildName, buildsPath, method } = parameters;
const command = `docker run \
--workdir /github/workspace \
--rm \
--env PROJECT_PATH=${projectPath} \
--env BUILD_TARGET=${platform} \
--env BUILD_NAME=${buildName} \
--env BUILDS_PATH=${buildsPath} \
--env BUILD_METHOD=${method} \
--env HOME=/github/home \
--env GITHUB_REF \
--env GITHUB_SHA \
--env GITHUB_REPOSITORY \
--env GITHUB_ACTOR \
--env GITHUB_WORKFLOW \
--env GITHUB_HEAD_REF \
--env GITHUB_BASE_REF \
--env GITHUB_EVENT_NAME \
--env GITHUB_WORKSPACE=/github/workspace \
--env GITHUB_ACTION \
--env GITHUB_EVENT_PATH \
--env RUNNER_OS \
--env RUNNER_TOOL_CACHE \
--env RUNNER_TEMP \
--env RUNNER_WORKSPACE \
--volume "/var/run/docker.sock":"/var/run/docker.sock" \
--volume "/home/runner/work/_temp/_github_home":"/github/home" \
--volume "/home/runner/work/_temp/_github_workflow":"/github/workflow" \
--volume "${workspace}":"/github/workspace" \
${image}`;
await exec(command, null, { silent });
}
}

35
src/model/docker.test.js Normal file
View File

@@ -0,0 +1,35 @@
import Action from './action';
import Docker from './docker';
import ImageTag from './image-tag';
describe('Docker', () => {
it('builds', async () => {
const path = Action.rootFolder;
const dockerfile = `${path}/Dockerfile`;
const baseImage = new ImageTag({
repository: '',
name: 'alpine',
version: '3',
platform: 'Test',
});
const tag = await Docker.build({ path, dockerfile, baseImage }, true);
expect(tag).toBeInstanceOf(ImageTag);
expect(tag.toString()).toStrictEqual('unity-builder:3');
}, 240000);
it.skip('runs', async () => {
const image = 'unity-builder:2019.2.11f1-webgl';
const parameters = {
workspace: Action.rootFolder,
projectPath: `${Action.rootFolder}/test-project`,
buildName: 'someBulidName',
buildsPath: 'build',
method: '',
};
await Docker.run(image, parameters);
});
});

83
src/model/image-tag.js Normal file
View File

@@ -0,0 +1,83 @@
import { has, get, trimEnd, trimStart } from 'lodash-es';
export default class ImageTag {
constructor(imageProperties) {
const {
repository = 'gableroux',
name = 'unity3d',
version = '2019.2.11f1',
platform,
} = imageProperties;
if (!ImageTag.versionPattern.test(version)) {
throw new Error(`Invalid version "${version}".`);
}
if (!has(ImageTag.targetPlatformToBuilderPlatformMap, platform)) {
throw new Error(`Platform "${platform}" is currently not supported.`);
}
const builderPlatform = get(
ImageTag.targetPlatformToBuilderPlatformMap,
platform,
ImageTag.builderPlatforms.generic,
);
Object.assign(this, { repository, name, version, platform, builderPlatform });
}
static get versionPattern() {
return /^20\d{2}\.\d\.\w{3,4}|3$/;
}
static get builderPlatforms() {
return {
generic: '',
webgl: 'webgl',
mac: 'mac',
windows: 'windows',
android: 'android',
ios: 'ios',
};
}
static get targetPlatformToBuilderPlatformMap() {
const { generic, webgl, mac, windows, android, ios } = ImageTag.builderPlatforms;
// @see: https://github.com/Unity-Technologies/UnityCsReference/blob/9034442437e6b5efe28c51d02e978a96a3ce5439/Editor/Mono/BuildTarget.cs
return {
Test: generic,
WebGL: webgl,
StandaloneOSX: mac,
StandaloneWindows: windows,
StandaloneWindows64: windows,
StandaloneLinux64: generic,
PS4: generic,
XboxOne: generic,
Switch: generic,
Android: android,
iOS: ios,
tvOS: generic,
Lumin: generic,
BJM: generic,
Stadia: generic,
WSAPlayer: generic,
Facebook: generic,
NoTarget: generic,
};
}
get tag() {
return trimEnd(`${this.version}-${this.builderPlatform}`, '-');
}
get image() {
return trimStart(`${this.repository}/${this.name}`, '/');
}
toString() {
const { image, tag } = this;
return `${image}:${tag}`;
}
}

View File

@@ -0,0 +1,67 @@
import ImageTag from './image-tag';
describe('UnityImageVersion', () => {
const some = {
repository: 'test1',
name: 'test2',
version: '2099.9.f9f9',
platform: 'Stadia',
builderPlatform: '',
};
const defaults = {
repository: 'gableroux',
name: 'unity3d',
image: 'gableroux/unity3d',
};
describe('constructor', () => {
it('can be called', () => {
const { platform } = some;
expect(() => new ImageTag({ platform })).not.toThrow();
});
it('accepts parameters and sets the right properties', () => {
const image = new ImageTag(some);
expect(image.repository).toStrictEqual(some.repository);
expect(image.name).toStrictEqual(some.name);
expect(image.version).toStrictEqual(some.version);
expect(image.platform).toStrictEqual(some.platform);
expect(image.builderPlatform).toStrictEqual(some.builderPlatform);
});
test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', version => {
expect(() => new ImageTag({ version, platform: some.platform })).not.toThrow();
});
test.each(['some version', '', 1, null])('throws for incorrect versions %p', version => {
const { platform } = some;
expect(() => new ImageTag({ version, platform })).toThrow();
});
test.each([undefined, 'nonExisting'])('throws for unsupported target %p', platform => {
expect(() => new ImageTag({ platform })).toThrow();
});
});
describe('toString', () => {
it('returns the correct version', () => {
const image = new ImageTag({ version: '2099.1.1111', platform: some.platform });
expect(image.toString()).toStrictEqual(`${defaults.image}:2099.1.1111`);
});
it('returns the specific build platform', () => {
const image = new ImageTag({ version: '2019.2.11f1', platform: 'WebGL' });
expect(image.toString()).toStrictEqual(`${defaults.image}:2019.2.11f1-webgl`);
});
it('returns no specific build platform for generic targetPlatforms', () => {
const image = new ImageTag({ platform: 'Stadia' });
expect(image.toString()).toStrictEqual(`${defaults.image}:2019.2.11f1`);
});
});
});

22
src/model/input.js Normal file
View File

@@ -0,0 +1,22 @@
const core = require('@actions/core');
export default class Input {
static getFromUser() {
// Input variables specified in workflows using "with" prop.
const version = core.getInput('unityVersion');
const platform = core.getInput('targetPlatform');
const projectPath = core.getInput('projectPath');
const buildName = core.getInput('buildName');
const buildsPath = core.getInput('buildsPath');
const buildMethod = core.getInput('buildMethod');
return {
version,
platform,
projectPath,
buildName,
buildsPath,
method: buildMethod,
};
}
}

9
src/model/input.test.js Normal file
View File

@@ -0,0 +1,9 @@
import Input from './input';
describe('Input', () => {
describe('getFromUser', () => {
it('does not throw', () => {
expect(() => Input.getFromUser()).not.toThrow();
});
});
});

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 6f4ffa2938b3439f92f3caaf6a8055cc
timeCreated: 1575145041

View File

@@ -1,144 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;
static class Builder
{
private static string EOL = Environment.NewLine;
private static void ParseCommandLineArguments(out Dictionary<string, string> providedArguments)
{
providedArguments = new Dictionary<string, string>();
string[] args = Environment.GetCommandLineArgs();
Console.WriteLine(
$"{EOL}" +
$"###########################{EOL}" +
$"# Parsing settings #{EOL}" +
$"###########################{EOL}" +
$"{EOL}"
);
// Extract flags with optional values
for (int current = 0, next = 1; current < args.Length; current++, next++) {
// Parse flag
bool isFlag = args[current].StartsWith("-");
if (!isFlag) continue;
string flag = args[current].TrimStart('-');
// Parse optional value
bool flagHasValue = next < args.Length && !args[next].StartsWith("-");
string value = flagHasValue ? args[next].TrimStart('-') : "";
// Assign
Console.WriteLine($"Found flag \"{flag}\" with value \"{value}\".");
providedArguments.Add(flag, value);
}
}
private static Dictionary<string, string> GetValidatedOptions()
{
ParseCommandLineArguments(out var validatedOptions);
if (!validatedOptions.TryGetValue("projectPath", out var projectPath)) {
Console.WriteLine("Missing argument -projectPath");
EditorApplication.Exit(110);
}
if (!validatedOptions.TryGetValue("buildTarget", out var buildTarget)) {
Console.WriteLine("Missing argument -buildTarget");
EditorApplication.Exit(120);
}
if (!Enum.IsDefined(typeof(BuildTarget), buildTarget)) {
EditorApplication.Exit(121);
}
if (!validatedOptions.TryGetValue("customBuildPath", out var customBuildPath)) {
Console.WriteLine("Missing argument -customBuildPath");
EditorApplication.Exit(130);
}
string defaultCustomBuildName = "TestBuild";
if (!validatedOptions.TryGetValue("customBuildName", out var customBuildName)) {
Console.WriteLine($"Missing argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName);
}
else if (customBuildName == "") {
Console.WriteLine($"Invalid argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName);
}
return validatedOptions;
}
public static void BuildProject()
{
// Gather values from args
var options = GetValidatedOptions();
// Gather values from project
var scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(s => s.path).ToArray();
// Define BuildPlayer Options
var buildOptions = new BuildPlayerOptions {
scenes = scenes,
locationPathName = options["customBuildPath"],
target = (BuildTarget) Enum.Parse(typeof(BuildTarget), options["buildTarget"]),
};
// Perform build
BuildReport buildReport = BuildPipeline.BuildPlayer(buildOptions);
// Summary
BuildSummary summary = buildReport.summary;
ReportSummary(summary);
// Result
BuildResult result = summary.result;
ExitWithResult(result);
}
private static void ReportSummary(BuildSummary summary)
{
Console.WriteLine(
$"{EOL}" +
$"###########################{EOL}" +
$"# Build results #{EOL}" +
$"###########################{EOL}" +
$"{EOL}" +
$"Duration: {summary.totalTime.ToString()}{EOL}" +
$"Warnings: {summary.totalWarnings.ToString()}{EOL}" +
$"Errors: {summary.totalErrors.ToString()}{EOL}" +
$"Size: {summary.totalSize.ToString()} bytes{EOL}" +
$"{EOL}"
);
}
private static void ExitWithResult(BuildResult result)
{
if (result == BuildResult.Succeeded) {
Console.WriteLine("Build succeeded!");
EditorApplication.Exit(0);
}
if (result == BuildResult.Failed) {
Console.WriteLine("Build failed!");
EditorApplication.Exit(101);
}
if (result == BuildResult.Cancelled) {
Console.WriteLine("Build cancelled!");
EditorApplication.Exit(102);
}
if (result == BuildResult.Unknown) {
Console.WriteLine("Build result is unknown!");
EditorApplication.Exit(103);
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: dc057061ce9f406aa6b57a62d67fe9c0
timeCreated: 1575145310

5893
yarn.lock Normal file

File diff suppressed because it is too large Load Diff