Jenkins基于unity的自动化打包

 

Jenkins的使用

       关于jenkins的环境配置,这里就不介绍了,百度里面的教程够多。做下搬运工,可以参考这篇文章https://blog.****.net/potato512/article/details/52289136   mac下强烈建议 使用 brew 去安装 jenkins,不然会有很多坑

brew 安装jenkins 时可能根据需要修改工作目录.

修改工作目录的步骤如下:

一,安装完成后千万不要打开浏览器。

二,打开控制台窗口输入    sudo vi  /etc/profile

三,这里需要注意的是这个文件在我这边的环境下默认为只读文件,可使用下面的命令修改其文件权限:  sudo chmod 775 /etc/prifile

四,修改成功后再使用第一条指令打开,在文件的最后添加下面内容: export JENKINS_HOME=(要修改的路径)  

     然后,按ESC键,退出到插入模式,在按:号键+x 命令回车  即可保存文件

五,执行  source /etc/profile   命令。执行完成重启系统,再打开jenkins就能看到其主目录已修改成功、


这里主要介绍一下jenkins的基本使用以及通过C#去调用jenkins API 在unity上进行远程操作jiekins

一,登陆Jenkins

  1. 打开浏览器,访问打包机IP的8080端口,账号密码后期可以分配生成,公用admin,u3d99

Jenkins基于unity的自动化打包

二,创建新的工程

 Jenkins基于unity的自动化打包Jenkins基于unity的自动化打包

     

输入 任务名称, 然后在最下面 复制那里输入一个已存在的工程名称,点击确定。就可以复制出一个与已存在工程参数一样的工程目录。进入新任务里面的设置修改一下SVN的地址参数即可 

三,Jenkins的基本配置

在配置Jenkins的过程中,有如下配置选项:

Jenkins基于unity的自动化打包

1 General

选择参数化构建过程,可以配置所需要的参数,这里参数比较多,就不一一例举了,

2 源码管理

  Jenkins基于unity的自动化打包

在源码管理里面配置好资源的地址(svn地址或git地址),设定好本地保存的文件夹,然后根据需要设置好checkout方式

SVN配置 checkout会有多个选项

Use‘svnupdate’ as much as possible

第一次发布的时候,会把工作目录下的所有文件清空,然后check-out一份完整的项目到工作目录下;

以后更新的时候,不会判断已有文件是否在svn里存在。比如工作目录下的文件123svn里不存在,那么更新的时候不会删除,不会判断工作目录下的文件是否被改动,只会判断svn是否有新版本需要更新。

Alwayscheck out a fresh copy

第一次发布的时候,会把工作目录下的所有文件清空,然后check-out一份完整的项目到工作目录下;每一次更新的时候,都会先清除工作目录下的所有文件,然后重新check-out一份完整的项目到工作目录下。

Emulateclean checkout by firstdeleting unversioned/ignored filesthen ‘svn update’

第一次发布的时候,会把工作目录下的所有文件清空,然后check-out一份完整的项目到工作目录下。以后更新的时候会判断工作目录下的文件是否在svn里存在,如果不存在则删除,如果存在且有新版本则更新。会判断工作目录下的文件是否被改动,不管有没有新版本,都会还原为svn上的最新版本。svn上删除了文件,更新的时候,工作目录里的此文件也会被删除。

Use‘svn update’ as much aspossiblewith‘svn revert’ before update

第一次发布的时候,会把工作目录下的所有文件清空,然后check-out一份完整的项目到工作目录下;先放弃所有本地修改,然后尽可能进行svn更新。避免本地修改导致本地项目与代码仓库不一致

我这里选择的是最后一个。但是在热更新的时候会有问题,打热更新的时候需要转换平台,而svn上的是默认的,热更包打完后本地存在的是转换平台后的资源。在下一次打包热更资源时,会导致本地资源与svn资源不一致的情况。所以下一次打包时,会checkout SVN上的资源,删除本地的资源。

3构建触发器

 Jenkins基于unity的自动化打包

 这里用的build priodically. 每天2:00定时触发

 PollSCM 是没隔一段时间会去检测svn上的变化,然后触发构建

 

4然后是最重要的构建

Jenkins基于unity的自动化打包

-batchmode -executeMethodPerformBuild.CommandLineBuild Platform-$Platform AppID-$AppID Version-$VersionIPAddress-$IPAddress  -quit

在方法中这样解析参数:

 Jenkins基于unity的自动化打包

附上unity上接受jenkins 的类

  public class PerformBuild
{
    static string[] GetBuildScenes()
    {
        List<string> names = new List<string>();
        
        foreach(EditorBuildSettingsScene e in EditorBuildSettings.scenes)
        {
            if(e==null)
                continue;
            
            if(e.enabled)
                names.Add(e.path);
        }
        return names.ToArray();
    }


    /// <summary>
    /// 此方法是从jienkins上接受  数据的 方法
    /// </summary>
    static void CommandLineBuild ()
    {
        try
        {         
  
                Debug.Log("Command line build\n------------------\n------------------");
                string[] scenes = GetBuildScenes();
               string path = "";//这里的路径是打包的路径, 定义
                Debug.Log(path);
                for (int i = 0; i < scenes.Length; ++i)
                {
                    Debug.Log(string.Format("Scene[{0}]: \"{1}\"", i, scenes[i]));
                }
               // ProjectPackageEditor.BuildByJenkins(GetJenkinsParameter("Platform"), GetJenkinsParameter("AppID"), GetJenkinsParameter("Version"), GetJenkinsParameter("IPAddress"));
                Debug.Log("Starting Build!");
                Debug.Log(GetJenkinsParameter("Platform"));
                if (PlatformAandroid())
                {
                    BuildPipeline.BuildPlayer(scenes, path + ".apk", BuildTarget.Android, BuildOptions.None);
                }
                else
                {
                    //BuildPipeline.BuildPlayer(scenes, path, BuildTarget.iOS, BuildOptions.AcceptExternalModificationsToPlayer);
                }
        }
        catch(Exception err)
        {
            Console.WriteLine("方法F中捕捉到:"+err.Message);
            throw;//重新抛出当前正在由catch块处理的异常err
        }
        finally
        {
            Debug.Log("---------->  I am copying!   <--------------");
        }
    }


    /// <summary>
    ///解释jekins 传输的参数
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    static string GetJenkinsParameter(string name)
    {
        foreach(string arg in Environment.GetCommandLineArgs())
        {
            if (arg.StartsWith(name))
            {
                return arg.Split("-"[0])[1];
            }
        }
        return null;
    }
}


三,jenkins 打包安卓apk过程

  mac机上下载AndroidSDK jdkndk。并配置好相应环境。在unity Preferences

置好 sdkjdkndk路径

  不在unity上设置相对应的路径是不能够转平台的。jenkins 打包时,必须要把unity的工程关掉

  注意:

1电脑上存在多版本的 jdk 时,打包过程中会导致打包失败,解决办法是删掉多余的jdk,只保留一个即可

  2jenkins打包打出的安卓文件并不是apk格式,所以需要改名后缀为apk才行,所以在生成apk路径的后面加了一个后缀名

  3 jenkins SVN拉下工程在本地进行分文件夹进行保存

 Jenkins基于unity的自动化打包

将svn拉下来的工程路径 与 平台名称进行组合。平台前面的名称随意

 

 

四,关于mac机上jenkins的打包相关的路径

  Jenkins基于unity的自动化打包

Workspace 是jenkins的工作目录

Workspace下面的子目录  除build外,其余都是对应的单个项目的目录

也就是jenkins创建任务的名称。创建任务后,会自动在workspace下生成对应名称的文件夹。

拿testAndorid来举例。TestAndroid是我新建的一个任务(名字无视,随便取,最好是对应工程的名称来区分)。 TestAndroid下面会对应4个文件夹,子目录的TestAndroid是存放安卓工程的目录,TestIOS是存放IOS工程的目录。(就是从svn上拉下来的工程,都是经过jenkins转平台后的工程。Android 目录是 存放打包成功后的 apk 包。 XCode 是存放打包后的苹果Xcode工程。

另外还有一个Build文件夹主要是用于打Xcode工程是,从里面拷贝sdk用的

 

六,关于通过jenkins api 远程访问并操作jenkins的实现

  目前是通过 用unity 通过http请求 jenkins的操作

  第一步,在jenkins是上设置访问权限,根据需要访问权限

  Jenkins基于unity的自动化打包

      Jenkins基于unity的自动化打包   

       取消勾选防站点请求。

      我这里使用的比较暴力的方式,选的任何用户可以使用任何事,也可以根据需要选择下面的安全设置.设置此权限的话,访问jenkins页面就不需要登录了,后面用api访问jenkins时就不用添加认证。

      也可以选择登录用户可以做任何事情,此选择需要登录才可以使用,用api访问jenkins时需要添加Basic认证才可以访问成功

 

 第二步,通过http请求特定的jenkins api 进行请求

    下面例举部分部分jenkins api 地址

    jenkinsUrl 是jenkins的地址

     jobName 是 请求的任务名称

    1获取build信息:

      url= jenkinsUrl + "/job/" +jobName + "/api/json?tree=builds[number]{0,10}";

  2 创建工程

     url= jenkinsUrl + "/createItem?name="+jobName

    创建工程时需要在请求上发送与线上对应的xml文件

    在某项目的xml地址如下

    http://jenkinsUrl/job/SiChuanQiPai/config.xml

  然后在unity本地保存一个 与之一样的格式 xml 文件

  Jenkins基于unity的自动化打包

  Xml 文件格式类似这个样子,创建新的job 时,修改一下xml里面的参数,然后保存在本地。然后请jenkinsUrl+ "/createItem?name=" +jobName 这个地址,将本地的xml文件传输过去。

调用下面的方法即可: 参数 filePath 为xml文件的路径

自己写的:(此方法 上传的文件 的格式必须为 UTF-8无BOM格式)

     privatestaticstringPostHttp2(string url, string filePath)

    {

        stringresponseContent;

        varmemStream = newMemoryStream();

        varwebRequest = (HttpWebRequest)WebRequest.Create(url);

        varfileStream = newFileStream(filePath, FileMode.Open,FileAccess.Read);

        // 设置属性

        webRequest.Method = "POST";

        webRequest.Timeout = 300000;

        webRequest.ContentType = "application/xml;charset=UTF-8";

        webRequest.KeepAlive = true;

        webRequest.UserAgent = "Test";  

        webRequest.Accept = "gzip,deflate";

        CredentialCachemycache = new CredentialCache();

        mycache.Add(new Uri(jenkinsUrl),"Basic", new NetworkCredential(username, password));

        webRequest.Credentials = mycache;

       webRequest.Headers.Add("Authorization", "Basic " +Convert.ToBase64String(new ASCIIEncoding().GetBytes(username + ":" +password)));

        // 写入文件

        varbuffer = newbyte[1024];

        intbytesRead; // =0

        while((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)

        {

            memStream.Write(buffer, 0,bytesRead);

        }

        webRequest.ContentLength =memStream.Length;

        try

        {

            varrequestStream = webRequest.GetRequestStream();

            memStream.Position = 0;

            vartempBuffer = newbyte[memStream.Length];

            memStream.Read(tempBuffer, 0,tempBuffer.Length);

            memStream.Close();

            requestStream.Write(tempBuffer, 0,tempBuffer.Length);

            requestStream.Close();

            varhttpWebResponse = (HttpWebResponse)webRequest.GetResponse();

            using (varhttpStreamReader = newStreamReader(httpWebResponse.GetResponseStream(),Encoding.GetEncoding("utf-8")))

            {

                responseContent =httpStreamReader.ReadToEnd();

            }

            fileStream.Close();

            httpWebResponse.Close();

            webRequest.Abort();

            returnresponseContent;

        }

        catch (WebException ex)

        {

           省略

        }

}

 

使用BestHttp插件的方法是如下:(此方法 上传的文件 的格式必须为 UTF-8格式)

      staticvoidPostHttpJenkins(stringurl,string filePath)

    {

        var request = newHTTPRequest(newUri(url), HTTPMethods.Post,(req, resp) =>

        {

           HttpPostResponseCode(resp.StatusCode);

            Debug.Log(resp.DataAsText);         

        });

        request.Credentials = newCredentials(AuthenticationTypes.Basic, username, password);

       

        var fileStream = newFileStream(filePath,FileMode.Open);

        var buffer = newbyte[fileStream.Length];

        request.SetHeader("Content-Type","application/xml; charset=UTF-8");      

        fileStream.Read(buffer, 0,buffer.Length);

        request.RawData = buffer;     

        request.Send();

        fileStream.Close();

    }

 

   3更新Jenkinsjob配置

       url= jenkinsUrl+ "/job/" + jobName + "/config.xml"

        更新配置时,与创建job类似,同样调用上面的PostHttp2 方法,将此url与修改的xml文件的地址传过去即可

   4 获取jenkins Job配置

        url=jenkinsUrl + "/job/" + jobName + "/config.xml"

     方法一:

          publicstaticstring GetHttp(string url) {

        HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);

        httpWebRequest.ContentType = "application/json";

        httpWebRequest.Method = "GET";

        httpWebRequest.Timeout = 20000;

        CredentialCache mycache = newCredentialCache();

        mycache.Add(newUri(jenkinsUrl),"Basic",newNetworkCredential(username, password));

        httpWebRequest.Credentials = mycache;

        httpWebRequest.Headers.Add("Authorization","Basic "+ Convert.ToBase64String(newASCIIEncoding().GetBytes(username + ":" + password)));

        HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();

        StreamReader streamReader = newStreamReader(httpWebResponse.GetResponseStream());

        string responseContent = streamReader.ReadToEnd();

        httpWebResponse.Close();

        streamReader.Close();

        return responseContent; }

     方法二:使用bestHTTP

        staticvoidGetHttpJenkins(stringurl)

    {

        var request = newHTTPRequest(newUri(url), HTTPMethods.Get,(req, resp) =>

        {

            HttpPostResponseCode(resp.StatusCode);

        });

        request.Credentials = newCredentials(AuthenticationTypes.Basic, username, password);

        request.Send();

     }

 

   5,BuildJenkins工程

      有参数地址:url= jenkinsUrl + "/job/" + jobName + "/buildWithParameters"

      无参数地址: url= jenkinsUrl + "/job/" + jobName + "/build"

     下面是有参数的调用方法(使用bestHTTP插件)

     publicstaticvoidBuildJenkins()

    {

        stringurlString = jenkinsUrl + "/job/" + jobName + "/buildWithParameters";

        FiltrationPath();

        varrequest = newHTTPRequest(newUri(urlString),HTTPMethods.Post, (req, resp) =>

        {

            Debug.Log(resp.StatusCode);

            if(resp.StatusCode != 401)

                Debug.Log("Authenticated");

            else

                Debug.Log("NOTAuthenticated");

               Debug.Log(resp.DataAsText);

        });

        //添加Basic认证

        request.Credentials =  newCredentials(AuthenticationTypes.Basic, username,password);

        request.AddField("Plantform",platformName);

        request.AddField("AppID",appID);

        request.AddField("VersionNumber",versionNumber);

        request.AddField("IPAddress",ipAddress);

        request.AddField("isHotFix",isHotFix?"true":"false");

        request.AddField("path",content);       

        request.Send();

    }

   注意这里有个大坑如果本身的工程是带有参数的,请求的是无参数地址的时候,会返回错误码  400

   另外:jenkin是有账号密码的,所以http请求是需要Credentials认证才能进行访问。有参数的http 请求的数据形式为 form data 形式。Form data格式上面的besthttp插件已经封装好了。

     Build热更资源前需要先转换平台

  6:关于请求jenkins 服务器返回的错误码

      privatestaticvoid HttpPostResponseCode(int errorCode)

     {

        switch (errorCode)

        {

            case 400:Debug.LogError("传递的参数不正确");

                break;

            case 401:Debug.LogError("Not Authenticated");

                break;

            case 403:Debug.LogError("未添加basic认证");

                break;

            case 500:Debug.LogError("传递的文件格式不对,需要为UTF-8格式(上面PostHttp2里面需要为UTF-8 无BOM 格式,具体原因还不清楚)");

                break;

        }

     }

 

 

     Xml文件操作的坑

Jenkins基于unity的自动化打包

Xml Linq查询节点

    var StringParameterDefinition = xDoc.Element("project")

        .Element("properties")

       .Element("hudson.model.ParametersDefinitionProperty")

       .Element("parameterDefinitions")

      .Element("hudson.model.TextParameterDefinition");

对xml文件进行操作时,version的版本要是1.0 ,不然操作会不成功。从jenkins网页上直接获取的的xml文件的version是1.1,所以操作会报错误

发送改xml到jenkins服务器上  ,会出现以下错误

Jenkins基于unity的自动化打包