Android-x86-6.0定製之路 - 如何管理源碼

前言

終於完成了 android-x86-6.0 源碼的編譯工作,經過簡單的測試,系統正常工作,接下來就是閱讀和修改源碼了。爲了方便修改、提交、測試源碼,想着應該將源碼上傳到 git,但是源碼過於龐大,如果將整個源碼作爲單個項目上傳 git 的話,必將造成每次同步都非常慢。想了下,覺得應該跟官方一樣採用 repo 管理源碼,將源碼拆成多個項目去維護。

搭建 GitLab 服務

想了下,還是先在服務器上搭建 GitLab 來管理源碼,如果以後有必要的話再遷移到公司的 GitLab 上去,現在就當練手吧。

更新軟件源

supo apt-get update

安裝 openssl

sudo apt-get install -y curl openssh-server ca-certificates

安裝 postfix

這裏是配置郵件服務,我跳過了

sudo apt-get install -y postfix

添加 GitLab 包

curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh | sudo bash

安裝 GitLab

sudo EXTERNAL_URL="http://gitlab.example.com" apt-get install gitlab-ee

注意http://gitlab.example.com更改爲要訪問 GitLab 實例的 URL,這裏我更改爲http://192.168.1.52

關於如何卸載 GitLab

# 停止gitlab
sudo gitlab-ctl stop

# 查看進程
ps -e | grep gitlab

# 刪除所有包含gitlab的文件及目錄
find / -name gitlab | xargs rm -rf

# 卸載
sudo apt-get remove gitlab-ee

# 檢查還有沒有卸載的gitlab相關軟件
dpkg --get-selections | grep gitlab
gitlab-ee deinstall

# 再執行
sudo apt-get --purge remove gitlab-ee

登錄 GitLab

在瀏覽器訪問http://192.168.1.52,首次訪問是以 root 用戶登錄需要設置用戶密碼。

這樣 GitLab 的服務,基本就搭建完成了。

修改 GitLab 配置

如果需要修改有關 GitLab 服務的相關配置,比如說修改訪問 GitLab 域名等

執行vi /etc/gitlab/gitlab.rb,編譯配置文件

編輯完成後,執行sudo gitlab-ctl reconfigure,讓配置生效

添加賬號

因爲使用郵件註冊賬號太過繁瑣,直接切換到 Root 用戶創建賬號。先創建兩個賬號,一個給我的 Mac Pro 使用,另一個 給 Ubuntu 服務器使用,並且都賦予管理員的權限。創建完成後,登錄並且設置 SSH Keys

上傳源碼至 GitLab

參考文檔

Repo 介紹

Repo 管理多個 git 倉庫

創建 manifest.git

manifest.git 倉庫是一個項目清單庫,用來管理 android-x86-6.0 源碼中的 git 倉庫,裏面標明瞭遠程倉庫地址、 項目倉庫的名稱和本地路徑等信息

1.在 GitLab 上創建名爲 Android-x86-6.0 的項目組, 創建名爲 manifest 的空倉庫

2.執行cd ~/android-x86-6.0指令,切換到源碼文件夾

3.執行[email protected]:android-x86-6.0/manifest.git,克隆倉庫

4.執行cd manfiest指令切換到 manfiest 文件夾,創建 default.xml 文件,並且編寫使用 repo 管理的 git 倉庫的名稱、倉庫在本地的路徑和倉庫的 url 地址

下面是我的default.xml內容,僅供參考:

<?xml version="1.0" encoding="UTF-8" ?>

<manifest>
    <remote name="origin" fetch="." />  <!-- ".", 表示使用repo init -u url裏的url,這裏指 [email protected]:android-x86-6.0/ -->
    <default remote="origin" revision="master"/>
    <project path="abi" name="abi"/>
    <project path="art" name="art"/>
    <project path="bionic" name="bionic"/>
    <project path="bootable" name="bootable"/>
    <project path="build" name="build">
      <copyfile dest="Makefile" src="core/root.mk"/>
    </project>
    <project path="dalvik" name="dalvik"/>
    <project path="development" name="development"/>
    <project path="device" name="device"/>
    <project path="external" name="external"/>
    <project path="frameworks" name="frameworks"/>
    <project path="hardware" name="hardware"/>
    <project path="kernel" name="kernel"/>
    <project path="libcore" name="libcore"/>
    <project path="libnativehelper" name="libnativehelper"/>
    <project path="ndk" name="ndk"/>
    <project path="packages" name="packages" />
    <project path="platform_testing" name="platform_testing"/>
    <project path="prebuilts" name="prebuilts"/>
    <project path="sdk" name="sdk"/>
    <project path="system" name="system"/>
</manifest>

remote 節點中 fetch: 表示遠程倉庫的 url,這裏的 url 是指整個源碼項目的名稱,像 [email protected]:android-x86-6.0/這樣

project 節點中 path: 表示倉庫在本地的相對路徑,是以 manifest 文件夾位置爲準的

project 節點中 name: 表示倉庫的名稱,這個名稱爲創建倉庫時的名稱。這裏將 name 與 remote 節點中 fetch 拼接就是倉庫的 url,像[email protected]:android-x86-6.0/abi.git這樣

default: 表示默認的遠程鏈接爲 origin,默認的同步的分支爲 master

5.執行git add . && git commit -m 'Initial commit' && git push指令,將default.xml文件推送到服務器上

這樣,manifest.git 倉庫的創建就算基本完成了

批量上傳項目

前面創建default.xml 時已經將源碼工程拆分成 20 個子項目,現在就是怎樣批量去上傳這 20 個項目了,而且上傳項目前,還要在 GitLab 上創建 20 個空的倉庫。如果這些工作手動來操作的話,那就太 Low 了,得寫腳本來自動執行。

下面是我用 Node.js 編寫的腳本,主要是用來自動遷移源碼工程至 GitLab 服務器

GitLabApi

GitLabApi 類,主要調用 Api 創建項目、創建用戶等

/**
 * GitLab Api simple example
 * doc: https://gitlab.com/help/api/README.md
 */
class GitLabApi {
  constructor(baseUrl, token) {
    this.baseUrl = baseUrl; // GitLab Server Address
    this.headers = {
      'PRIVATE-TOKEN': token // GitLab Account Token
    };
  }

  [validhttpstatuscode](code) {
    return code >= 200 && code < 300;
  }

  /**
   * @returns Promise
   */
  groupList() {
    const url = `${this.baseUrl}/groups`;
    const options = {
      headers: this.headers
    };
    return new Promise((resolve, reject) => {
      request.get(url, options, (err, res, body) => {
        if (this[validhttpstatuscode](res.statusCode)) {
          console.log('--- group list query success ---');
          resolve(body);
        } else {
          console.log('--- group list query fail ---');
          reject(err);
        }
      });
    });
  }

  /**
   *
   * @param {*} group_name
   * @returns Promise
   */
  newGroup(group_name) {
    const group_path = group_name.charAt(0).toLowerCase() + group_name.slice(1);
    const url = `${this.baseUrl}/groups?name=${group_name}&path=${group_path}&visibility=private`;
    const options = {
      headers: this.headers
    };
    return new Promise((resolve, reject) => {
      request.post(url, options, (err, res, body) => {
        if (this[validhttpstatuscode](res.statusCode)) {
          console.log(`--- group: ${group_name}, create success ---`);
          resolve(body);
        } else {
          console.log(`--- group: ${group_name}, create fail ---`);
          reject(err);
        }
      });
    });
  }

  /**
   *
   * @param {*} id
   * @returns Promise
   */
  delGroup(id) {
    const url = `${this.baseUrl}/groups/${id}`;
    const options = {
      headers: this.headers
    };
    return new Promise((resolve, reject) => {
      request.delete(url, options, (err, res, body) => {
        if (this[validhttpstatuscode](res.statusCode)) {
          console.log(`group: ${id}, delete success`);
          resolve(body);
        } else {
          console.log(`group: ${id}, delete fail`);
          reject(err);
        }
      });
    });
  }

  /**
   * @returns Promise
   */
  projectList() {
    const url = `${this.baseUrl}/projects`;
    const options = {
      headers: this.headers
    };
    return new Promise((resolve, reject) => {
      request.get(url, options, (err, res, body) => {
        if (this[validhttpstatuscode](res.statusCode)) {
          console.log('--- project list query success ---');
          resolve(body);
        } else {
          console.log('--- project list query fail ---');
          reject(err);
        }
      });
    });
  }

  /**
   *
   * @param {*} group_id
   * @param {*} name
   * @returns Promise
   */
  newProject(group_id, name) {
    const url = group_id
      ? `${this.baseUrl}/projects?namespace_id=${group_id}&name=${name}`
      : `${this.baseUrl}/projects?name=${name}`;
    const options = {
      headers: this.headers
    };
    return new Promise((resolve, reject) => {
      request.post(url, options, (err, res, body) => {
        if (this[validhttpstatuscode](res.statusCode)) {
          console.log(`project: ${name}, create success`);
          resolve(body);
        } else {
          console.log(`project: ${name}, create fail`);
          reject(err);
        }
      });
    });
  }

  /**
   *
   * @param {*} id
   * @returns Promise
   */
  delProject(id) {
    const url = `${this.baseUrl}/projects/${id}`;
    const options = {
      headers: this.headers
    };
    return new Promise((resolve, reject) => {
      request.delete(url, options, (err, res, body) => {
        if (this[validhttpstatuscode](res.statusCode)) {
          console.log(`project: ${id}, delete success`);
          resolve(body);
        } else {
          console.log(`project: ${id}, delete fail`);
          reject(err);
        }
      });
    });
  }

  /**
   * @returns Promise
   */
  userList() {
    const url = `${this.baseUrl}/users`;
    const options = {
      headers: this.headers
    };
    return new Promise((resolve, reject) => {
      request.get(url, options, (err, res, body) => {
        if (this[validhttpstatuscode](res.statusCode)) {
          console.log('--- user list query success ---');
          resolve(body);
        } else {
          console.log('--- user list query fail ---');
          reject(err);
        }
      });
    });
  }

  /**
   *
   * @param {*} username
   * @returns Promise
   */
  userInfo(username) {
    const url = `${this.baseUrl}/users?$search=${username}`;
    const options = {
      headers: this.headers
    };
    return new Promise((resolve, reject) => {
      request.get(url, options, (err, res, body) => {
        if (this[validhttpstatuscode](res.statusCode)) {
          console.log('--- userinfo query success ---');
          resolve(body);
        } else {
          console.log('--- userinfo query fail ---');
          reject(err);
        }
      });
    });
  }

  /**
   *
   * @param {*} password
   * @param {*} email
   * @param {*} username
   * @param {*} name
   * @returns Promise
   */
  newUser(password, email, username, name) {
    const url = `${baseUrl}/users?password=${password}&email=${email}&username=${username}&name=${name}`;
    const options = {
      headers: this.headers
    };
    request.post(url, options, (err, res, body) => {
      if (this[validhttpstatuscode](res.statusCode)) {
        console.log('--- add user success ---');
        resolve(body);
      } else {
        console.log('--- add user fail ---');
        reject(err);
      }
    });
  }

  /**
   *
   * @param {*} id
   * @returns Promise
   */
  delUser(id) {
    const url = `${this.baseUrl}/users/${id}`;
    const options = {
      headers: this.headers
    };
    return new Promise((resolve, reject) => {
      request.delete(url, options, (err, res, body) => {
        if (this[validhttpstatuscode](res.statusCode)) {
          console.log(`--- user: ${id}, delete success ---`);
          resolve(body);
        } else {
          console.log(`--- user: ${id}, delete fail ---`);
          reject(err);
        }
      });
    });
  }
}

Utils

Utils 類主要是查詢項目列表、執行 Linux 指令等

const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
class Utils {
  static input(question, isClose = false) {
    if (!rl) rl = readline.createInterface({ input: process.stdin, output: process.stdout });
    return new Promise((resolve, reject) => {
      rl.question(question, answer => {
        if (isClose) rl.close();
        resolve(answer);
      });
    });

    // // --- simple example ---
    // if (!rl) rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: '請輸入' });
    // rl.prompt();
    // rl.on('line', line => {
    //   console.log(line);
    //   resolve(line);
    //   rl.close();
    // }).on('close', () => {
    //   console.log('close');
    //   process.exit();
    // });
  }
  /**
   * get projectname
   * @returns Promise
   */
  static getProjectNameList() {
    const path = `${__dirname}/manifest.xml`;
    const xml = fs.readFileSync(path).toString();
    let list = [];
    const names = xml.match(/<project.+/g);
    for (let i = 0; i < names.length; i++) {
      list.push(names[i].match(/name="\w+"/g)[0].replace(/name="|"/g, ''));
    }
    console.log(`--- repo project list: total ${list.length} ---\n`, list);
    return list;

    // // --- async ---
    // const path = `${__dirname}/manifest.xml`;
    // const rl = readline.createInterface({ input: fs.createReadStream(path) });
    // const list = [];
    // return new Promise((resolve, reject) => {
    //   rl.on('line', line => {
    //     try {
    //       if (line.includes('<project')) {
    //         const name = line.match(/name="\w+"/g)[0].replace(/name="|"/g, '');
    //         list.push(name);
    //       }
    //     } catch (error) {
    //       reject(error);
    //     }
    //   });
    //   rl.on('close', () => {
    //     console.log(`--- repo project list: total ${list.length} ---\n`, list);
    //     resolve(list);
    //   });
    // });
  }

  /**
   * run cmd
   * @param {*} cmd
   * @returns Promise
   */
  static execCmd(cmd) {
    // --- sync ---
    try {
      const result = childprocess.execSync(cmd).toString();
      console.log(`--- "${cmd}", exec success ---`);
      return result;
    } catch (error) {
      console.log(`--- "${cmd}", exec fail ---`);
      console.log(error);
      return null;
    }

    // // --- async ---
    //   return new Promise((resolve, reject) => {
    //     childprocess.exec(cmd, (error, stdout, stderr) => {
    //       if (!error) {
    //         console.log(`"${cmd}", exec success`);
    //         resolve(stdout);
    //       } else {
    //         console.log(`--- "${cmd}", exec fail ---`);
    //         reject(stderr);
    //       }
    //     });
    //   });
  }
}

start

start 方法就是腳本的入口,爲了人性化,腳本編寫爲終端可交互型的,通過問答式接收參數。

async function start() {
  // 1. init args
  let gitlab_api_url = await Utils.input(
    'please input your gitlab api address ? default: http://192.168.1.52/api/v4 \n'
  );
  if (!gitlab_api_url) gitlab_api_url = 'http://192.168.1.52/api/v4';

  let gitlab_account_private_token = await Utils.input(
    'please input your gitlab account private-token ? default: R1G9zxaXG-a9rbrp-ZZA \n'
  );
  if (!gitlab_account_private_token) gitlab_account_private_token = 'R1G9zxaXG-a9rbrp-ZZA';

  let gitlab_group_name = await Utils.input('please input your gitlab group name ? default: Android-x86-6.0 \n');
  if (!gitlab_group_name) gitlab_group_name = 'Android-x86-6.0';

  let gitlab_group_workspace_url = await Utils.input(
    'please input your gitlab group workspace url ? default: [email protected]:android-x86-6.0 \n'
  );
  if (!gitlab_group_workspace_url) gitlab_group_workspace_url = '[email protected]:android-x86-6.0';

  local_workspace_url = await Utils.input(
    'please input your local workspace url ? default: /Users/barry/Android/SourceCode/android-x86-6.0 \n',
    true
  );
  if (!local_workspace_url) local_workspace_url = '/Users/barry/Android/SourceCode/android-x86-6.0';

  // 2. batch create project
  const api = new GitLabApi(gitlab_api_url, gitlab_account_private_token);
  const res = await api.newGroup(gitlab_group_name);
  const { id } = JSON.parse(res);
  const names = Utils.getProjectNameList();
  names.push('manifest'); // remeber create manifest project
  const result = await Promise.all(names.map(name => api.newProject(id, name)));

  if (result) {
    // 3. create manifest
    Utils.execCmd(`cd ${local_workspace_url} && git clone ${gitlab_group_workspace_url}/manifest.git`);
    const manifestData = fs.readFileSync(`${__dirname}/manifest.xml`).toString();
    fs.writeFileSync(`${local_workspace_url}/manifest/default.xml`, manifestData);
    const migrateData = fs.readFileSync(`${__dirname}/migrate.js`).toString();
    fs.writeFileSync(`${local_workspace_url}/manifest/migrate.js`, migrateData);
    fs.writeFileSync(`${local_workspace_url}/manifest/.gitignore`, '.DS_Store');
    Utils.execCmd(`cd ${local_workspace_url}/manifest && git add . && git commit -m 'Initial commit' && git push`);
    names.pop(); // create manifest success, remeber remove manifest project from names

    // 4. del local .repo folder
    if (fs.existsSync(`${local_workspace_url}/.repo`)) {
      Utils.execCmd(`rm -rf ${local_workspace_url}/.repo`);
    }

    // 5. del local git info
    let result = Utils.execCmd(`find ${local_workspace_url} -name '.gitignore' -or -name '.git'`);
    result = result.split('\n'); // string transform arry
    result.forEach(path => {
      if (path.replace(/(^\s*)|(\s*$)/g, '') && !path.startsWith('./manifest')) {
        const stat = fs.statSync(path);
        if (stat.isDirectory()) {
          Utils.execCmd(`rm -rf ${path}`);
        } else if (stat.isFile()) {
          Utils.execCmd(`mv ${path} ${path}.bak`);
        }
      }
    });

    // 6. push soruce code
    names.forEach(async name => {
      const dirs = fs.readdirSync(`${local_workspace_url}/${name}`);
      if (dirs.includes('.git')) {
        console.log(`--- del default .git folder in ${name} ---`);
        Utils.execCmd(`rm -rf ${local_workspace_url}/${name}/.git`);
      }
      if (!dirs.includes('.gitignore')) {
        console.log(`--- create .gitignore file in ${name} ---`);
        fs.writeFileSync(`${local_workspace_url}/${name}/.gitignore`, '.DS_Store');
      }
      // const cmd = `cd ${local_workspace_url}/${name} && git init && git remote add origin ${gitlab_group_workspace_url}/${name}.git && git add . && git commit -m 'Initial commit' && git push -u origin master`;
      Utils.execCmd(`cd ${local_workspace_url}/${name} && git init`);
      Utils.execCmd(
        `cd ${local_workspace_url}/${name} && git remote add origin ${gitlab_group_workspace_url}/${name}.git`
      );
      Utils.execCmd(`cd ${local_workspace_url}/${name} && git add .`);
      Utils.execCmd(`cd ${local_workspace_url}/${name} && git commit -m 'Initial commit'`);
      Utils.execCmd(`cd ${local_workspace_url}/${name} && git push -u origin master`);
    });

    // 7. repo init from your gitlab
    Utils.execCmd(
      `cd ${local_workspace_url} && repo init -u ${gitlab_group_workspace_url}/manifest.git && repo sync --force-sync`
    );
    console.log('--- congratulations,migrate success ----');
  }
}

說明 此腳本需要在 node.js 的環境運行,終端下執行node migrate.js即可

大概就這麼多,此腳本經個人測試過,完美的將整個 Android-x86-6.0 的源碼上傳至 GitLab 服務器。

如果需要完整的腳本,請前往下載

從 GitLab 下載源碼

前面終於把 Android-x86-6.0 的源碼上傳到 GitLab,現在需要在 Ubuntu 服務器上下載 Android-x86-6.0 的源碼

1.安裝 git 工具

2.安裝 repo 工具

3.GitLab 配置 SSH Keys

4.執行repo init -u [email protected]:android-x86-6.0/manifest.git初始化初始化倉庫

5.執行repo sync指令同步源碼

總結

這樣整個 Android-x86-6.0 的源碼遷移工作就完成了,當中學習了 GitLab 服務的搭建和 NodeJS 的知識,特別是加強了編寫腳本的能力,算是點小收穫。同時,以後也要多寫腳本,拒絕做重複的工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章